• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.messaging.widget;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.res.Resources;
22 import android.database.Cursor;
23 import android.graphics.Typeface;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.text.Spannable;
27 import android.text.SpannableStringBuilder;
28 import android.text.TextPaint;
29 import android.text.TextUtils;
30 import android.text.style.ForegroundColorSpan;
31 import android.text.style.StyleSpan;
32 import android.view.View;
33 import android.widget.RemoteViews;
34 import android.widget.RemoteViewsService;
35 
36 import com.android.messaging.R;
37 import com.android.messaging.datamodel.MessagingContentProvider;
38 import com.android.messaging.datamodel.data.ConversationListData;
39 import com.android.messaging.datamodel.data.ConversationListItemData;
40 import com.android.messaging.sms.MmsUtils;
41 import com.android.messaging.ui.UIIntents;
42 import com.android.messaging.ui.conversationlist.ConversationListItemView;
43 import com.android.messaging.util.ContentType;
44 import com.android.messaging.util.Dates;
45 import com.android.messaging.util.LogUtil;
46 import com.android.messaging.util.OsUtil;
47 import com.android.messaging.util.PhoneUtils;
48 
49 public class WidgetConversationListService extends RemoteViewsService {
50     private static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
51 
52     @Override
onGetViewFactory(Intent intent)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 
WidgetConversationListFactory(Context context, Intent intent)65         public WidgetConversationListFactory(Context context, Intent intent) {
66             super(context, intent);
67         }
68 
69         @Override
doQuery()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
getViewAt(int position)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 =
163                         conv.getIsFailedStatus() && !conv.getIsGroup() && isDefaultSmsApp;
164                 final boolean showDraft = conv.getShowDraft() && isDefaultSmsApp;
165                 remoteViews.setViewVisibility(R.id.conversation_failed_status_icon,
166                         showError && includeAvatar ?
167                         View.VISIBLE : View.GONE);
168 
169                 if (showError || showDraft) {
170                     remoteViews.setViewVisibility(R.id.snippet, View.GONE);
171                     remoteViews.setViewVisibility(R.id.errorBlock, View.VISIBLE);
172                     remoteViews.setTextViewText(R.id.errorSnippet, getSnippetText(conv));
173 
174                     if (showDraft) {
175                         // Show italicized "Draft" on third line
176                         final String text = resources.getString(
177                                 R.string.conversation_list_item_view_draft_message);
178                         SpannableStringBuilder builder = new SpannableStringBuilder(text);
179                         builder.setSpan(new StyleSpan(Typeface.ITALIC), 0, text.length(),
180                                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
181                         builder.setSpan(new ForegroundColorSpan(
182                                     resources.getColor(R.color.widget_text_color)),
183                                 0, text.length(),
184                                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
185                         remoteViews.setTextViewText(R.id.errorText, builder);
186                     } else {
187                         // Show error message on third line
188                        int failureMessageId = R.string.message_status_download_failed;
189                         if (conv.getIsMessageTypeOutgoing()) {
190                             failureMessageId = MmsUtils.mapRawStatusToErrorResourceId(
191                                     conv.getMessageStatus(),
192                                     conv.getMessageRawTelephonyStatus());
193                         }
194                         remoteViews.setTextViewText(R.id.errorText,
195                                 resources.getString(failureMessageId));
196                     }
197                 } else {
198                     remoteViews.setViewVisibility(R.id.errorBlock, View.GONE);
199                     remoteViews.setViewVisibility(R.id.snippet, View.VISIBLE);
200                     remoteViews.setTextViewText(R.id.snippet,
201                             boldifyIfUnread(getSnippetText(conv), hasUnreadMessages));
202                 }
203 
204                 // Set the accessibility TalkBack text
205                 remoteViews.setContentDescription(R.id.widget_conversation_list_item,
206                         ConversationListItemView.buildContentDescription(mContext.getResources(),
207                                 conv, new TextPaint()));
208 
209                 return remoteViews;
210             }
211         }
212 
getSnippetText(final ConversationListItemData conv)213         private String getSnippetText(final ConversationListItemData conv) {
214             String snippetText = conv.getShowDraft() ?
215                     conv.getDraftSnippetText() : conv.getSnippetText();
216             final String previewContentType = conv.getShowDraft() ?
217                     conv.getDraftPreviewContentType() : conv.getPreviewContentType();
218             if (TextUtils.isEmpty(snippetText)) {
219                 Resources resources = mContext.getResources();
220                 // Use the attachment type as a snippet so the preview doesn't look odd
221                 if (ContentType.isAudioType(previewContentType)) {
222                     snippetText = resources.getString(
223                             R.string.conversation_list_snippet_audio_clip);
224                 } else if (ContentType.isImageType(previewContentType)) {
225                     snippetText = resources.getString(R.string.conversation_list_snippet_picture);
226                 } else if (ContentType.isVideoType(previewContentType)) {
227                     snippetText = resources.getString(R.string.conversation_list_snippet_video);
228                 } else if (ContentType.isVCardType(previewContentType)) {
229                     snippetText = resources.getString(R.string.conversation_list_snippet_vcard);
230                 }
231             }
232             return snippetText;
233         }
234 
235         /**
236          * @return the "View more conversations" view. When the user taps this item, they're
237          * taken to the Bugle's conversation list.
238          */
239         @Override
getViewMoreItemsView()240         protected RemoteViews getViewMoreItemsView() {
241             if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
242                 LogUtil.v(TAG, "getViewMoreItemsView");
243             }
244             final RemoteViews view = new RemoteViews(mContext.getPackageName(),
245                     R.layout.widget_loading);
246             view.setTextViewText(
247                     R.id.loading_text, mContext.getText(R.string.view_more_conversations));
248 
249             // Tapping this "More conversations" item should take us to the ConversationList.
250             // However, the list view is primed with an intent to go to the Conversation activity.
251             // Each normal conversation list item sets the fill-in intent with the
252             // ConversationId for that particular conversation. In other words, the only place
253             // we can go is the ConversationActivity. We add an extra here to tell the
254             // ConversationActivity to really take us to the ConversationListActivity.
255             final Intent intent = new Intent();
256             intent.putExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, true);
257             view.setOnClickFillInIntent(R.id.widget_loading, intent);
258             return view;
259         }
260 
261         @Override
getLoadingView()262         public RemoteViews getLoadingView() {
263             RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading);
264             view.setTextViewText(
265                     R.id.loading_text, mContext.getText(R.string.loading_conversations));
266             return view;
267         }
268 
269         @Override
getViewTypeCount()270         public int getViewTypeCount() {
271             return 2;
272         }
273 
274         @Override
getMainLayoutId()275         protected int getMainLayoutId() {
276             return R.layout.widget_conversation_list;
277         }
278     }
279 
280 }
281