Home | History | Annotate | Download | only in widget
      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
     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