1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.messaging.widget;
18
19import android.content.Context;
20import android.content.Intent;
21import android.content.res.Resources;
22import android.database.Cursor;
23import android.graphics.Typeface;
24import android.net.Uri;
25import android.os.Bundle;
26import android.text.Spannable;
27import android.text.SpannableStringBuilder;
28import android.text.TextPaint;
29import android.text.TextUtils;
30import android.text.style.ForegroundColorSpan;
31import android.text.style.StyleSpan;
32import android.view.View;
33import android.widget.RemoteViews;
34import android.widget.RemoteViewsService;
35
36import com.android.messaging.R;
37import com.android.messaging.datamodel.MessagingContentProvider;
38import com.android.messaging.datamodel.data.ConversationListData;
39import com.android.messaging.datamodel.data.ConversationListItemData;
40import com.android.messaging.sms.MmsUtils;
41import com.android.messaging.ui.UIIntents;
42import com.android.messaging.ui.conversationlist.ConversationListItemView;
43import com.android.messaging.util.ContentType;
44import com.android.messaging.util.Dates;
45import com.android.messaging.util.LogUtil;
46import com.android.messaging.util.OsUtil;
47import com.android.messaging.util.PhoneUtils;
48
49public class WidgetConversationListService extends RemoteViewsService {
50    private static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
51
52    @Override
53    public RemoteViewsFactory onGetViewFactory(Intent intent) {
54        if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
55            LogUtil.v(TAG, "onGetViewFactory intent: " + intent);
56        }
57        return new WidgetConversationListFactory(getApplicationContext(), intent);
58    }
59
60    /**
61     * Remote Views Factory for Bugle Widget.
62     */
63    private static class WidgetConversationListFactory extends BaseWidgetFactory {
64
65        public WidgetConversationListFactory(Context context, Intent intent) {
66            super(context, intent);
67        }
68
69        @Override
70        protected Cursor doQuery() {
71            return  mContext.getContentResolver().query(MessagingContentProvider.CONVERSATIONS_URI,
72                    ConversationListItemData.PROJECTION,
73                    ConversationListData.WHERE_NOT_ARCHIVED,
74                    null,       // selection args
75                    ConversationListData.SORT_ORDER);
76        }
77
78        /**
79         * @return the {@link RemoteViews} for a specific position in the list.
80         */
81        @Override
82        public RemoteViews getViewAt(int position) {
83            if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
84                LogUtil.v(TAG, "getViewAt position: " + position);
85            }
86            synchronized (sWidgetLock) {
87                // "View more conversations" view.
88                if (mCursor == null
89                        || (mShouldShowViewMore && position >= getItemCount())) {
90                    return getViewMoreItemsView();
91                }
92
93                if (!mCursor.moveToPosition(position)) {
94                    // If we ever fail to move to a position, return the "View More conversations"
95                    // view.
96                    LogUtil.w(TAG, "Failed to move to position: " + position);
97                    return getViewMoreItemsView();
98                }
99
100                final ConversationListItemData conv = new ConversationListItemData();
101                conv.bind(mCursor);
102
103                // Inflate and fill out the remote view
104                final RemoteViews remoteViews = new RemoteViews(
105                        mContext.getPackageName(), R.layout.widget_conversation_list_item);
106
107                final boolean hasUnreadMessages = !conv.getIsRead();
108                final Resources resources = mContext.getResources();
109                final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp();
110
111                final String timeStamp = conv.getIsSendRequested() ?
112                        resources.getString(R.string.message_status_sending) :
113                            Dates.getWidgetTimeString(conv.getTimestamp(), true /*abbreviated*/)
114                                .toString();
115                // Date/Timestamp or Sending or Error state -- all shown in the date item
116                remoteViews.setTextViewText(R.id.date,
117                        boldifyIfUnread(timeStamp, hasUnreadMessages));
118
119                // From
120                remoteViews.setTextViewText(R.id.from,
121                        boldifyIfUnread(conv.getName(), hasUnreadMessages));
122
123                // Notifications turned off mini-bell icon
124                remoteViews.setViewVisibility(R.id.conversation_notification_bell,
125                        conv.getNotificationEnabled() ? View.GONE : View.VISIBLE);
126
127                // On click intent.
128                final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext,
129                        conv.getConversationId(), null /* draft */);
130
131                remoteViews.setOnClickFillInIntent(R.id.widget_conversation_list_item, intent);
132
133                // Avatar
134                boolean includeAvatar;
135                if (OsUtil.isAtLeastJB()) {
136                    final Bundle options = mAppWidgetManager.getAppWidgetOptions(mAppWidgetId);
137                    if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
138                        LogUtil.v(TAG, "getViewAt BugleWidgetProvider.WIDGET_SIZE_KEY: " +
139                                options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY));
140                    }
141
142                    includeAvatar = options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY) ==
143                            BugleWidgetProvider.SIZE_LARGE;
144                } else {
145                    includeAvatar = true;;
146                }
147
148                // Show the avatar when grande size, otherwise hide it.
149                remoteViews.setViewVisibility(R.id.avatarView, includeAvatar ?
150                        View.VISIBLE : View.GONE);
151
152                Uri iconUri = null;
153                if (conv.getIcon() != null) {
154                    iconUri = Uri.parse(conv.getIcon());
155                }
156                remoteViews.setImageViewBitmap(R.id.avatarView, includeAvatar ?
157                        getAvatarBitmap(iconUri) : null);
158
159                // Error
160                // Only show the fail icon if it is not a group conversation.
161                // And also require that we be the default sms app.
162                final boolean showError = conv.getIsFailedStatus() &&
163                        isDefaultSmsApp;
164                final boolean showDraft = conv.getShowDraft() &&
165                        isDefaultSmsApp;
166                remoteViews.setViewVisibility(R.id.conversation_failed_status_icon,
167                        showError && includeAvatar ?
168                        View.VISIBLE : View.GONE);
169
170                if (showError || showDraft) {
171                    remoteViews.setViewVisibility(R.id.snippet, View.GONE);
172                    remoteViews.setViewVisibility(R.id.errorBlock, View.VISIBLE);
173                    remoteViews.setTextViewText(R.id.errorSnippet, getSnippetText(conv));
174
175                    if (showDraft) {
176                        // Show italicized "Draft" on third line
177                        final String text = resources.getString(
178                                R.string.conversation_list_item_view_draft_message);
179                        SpannableStringBuilder builder = new SpannableStringBuilder(text);
180                        builder.setSpan(new StyleSpan(Typeface.ITALIC), 0, text.length(),
181                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
182                        builder.setSpan(new ForegroundColorSpan(
183                                    resources.getColor(R.color.widget_text_color)),
184                                0, text.length(),
185                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
186                        remoteViews.setTextViewText(R.id.errorText, builder);
187                    } else {
188                        // Show error message on third line
189                       int failureMessageId = R.string.message_status_download_failed;
190                        if (conv.getIsMessageTypeOutgoing()) {
191                            failureMessageId = MmsUtils.mapRawStatusToErrorResourceId(
192                                    conv.getMessageStatus(),
193                                    conv.getMessageRawTelephonyStatus());
194                        }
195                        remoteViews.setTextViewText(R.id.errorText,
196                                resources.getString(failureMessageId));
197                    }
198                } else {
199                    remoteViews.setViewVisibility(R.id.errorBlock, View.GONE);
200                    remoteViews.setViewVisibility(R.id.snippet, View.VISIBLE);
201                    remoteViews.setTextViewText(R.id.snippet,
202                            boldifyIfUnread(getSnippetText(conv), hasUnreadMessages));
203                }
204
205                // Set the accessibility TalkBack text
206                remoteViews.setContentDescription(R.id.widget_conversation_list_item,
207                        ConversationListItemView.buildContentDescription(mContext.getResources(),
208                                conv, new TextPaint()));
209
210                return remoteViews;
211            }
212        }
213
214        private String getSnippetText(final ConversationListItemData conv) {
215            String snippetText = conv.getShowDraft() ?
216                    conv.getDraftSnippetText() : conv.getSnippetText();
217            final String previewContentType = conv.getShowDraft() ?
218                    conv.getDraftPreviewContentType() : conv.getPreviewContentType();
219            if (TextUtils.isEmpty(snippetText)) {
220                Resources resources = mContext.getResources();
221                // Use the attachment type as a snippet so the preview doesn't look odd
222                if (ContentType.isAudioType(previewContentType)) {
223                    snippetText = resources.getString(
224                            R.string.conversation_list_snippet_audio_clip);
225                } else if (ContentType.isImageType(previewContentType)) {
226                    snippetText = resources.getString(R.string.conversation_list_snippet_picture);
227                } else if (ContentType.isVideoType(previewContentType)) {
228                    snippetText = resources.getString(R.string.conversation_list_snippet_video);
229                } else if (ContentType.isVCardType(previewContentType)) {
230                    snippetText = resources.getString(R.string.conversation_list_snippet_vcard);
231                }
232            }
233            return snippetText;
234        }
235
236        /**
237         * @return the "View more conversations" view. When the user taps this item, they're
238         * taken to the Bugle's conversation list.
239         */
240        @Override
241        protected RemoteViews getViewMoreItemsView() {
242            if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
243                LogUtil.v(TAG, "getViewMoreItemsView");
244            }
245            final RemoteViews view = new RemoteViews(mContext.getPackageName(),
246                    R.layout.widget_loading);
247            view.setTextViewText(
248                    R.id.loading_text, mContext.getText(R.string.view_more_conversations));
249
250            // Tapping this "More conversations" item should take us to the ConversationList.
251            // However, the list view is primed with an intent to go to the Conversation activity.
252            // Each normal conversation list item sets the fill-in intent with the
253            // ConversationId for that particular conversation. In other words, the only place
254            // we can go is the ConversationActivity. We add an extra here to tell the
255            // ConversationActivity to really take us to the ConversationListActivity.
256            final Intent intent = new Intent();
257            intent.putExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, true);
258            view.setOnClickFillInIntent(R.id.widget_loading, intent);
259            return view;
260        }
261
262        @Override
263        public RemoteViews getLoadingView() {
264            RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading);
265            view.setTextViewText(
266                    R.id.loading_text, mContext.getText(R.string.loading_conversations));
267            return view;
268        }
269
270        @Override
271        public int getViewTypeCount() {
272            return 2;
273        }
274
275        @Override
276        protected int getMainLayoutId() {
277            return R.layout.widget_conversation_list;
278        }
279    }
280
281}
282