/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.messaging.widget; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextPaint; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.view.View; import android.widget.RemoteViews; import android.widget.RemoteViewsService; import com.android.messaging.R; import com.android.messaging.datamodel.MessagingContentProvider; import com.android.messaging.datamodel.data.ConversationListData; import com.android.messaging.datamodel.data.ConversationListItemData; import com.android.messaging.sms.MmsUtils; import com.android.messaging.ui.UIIntents; import com.android.messaging.ui.conversationlist.ConversationListItemView; import com.android.messaging.util.ContentType; import com.android.messaging.util.Dates; import com.android.messaging.util.LogUtil; import com.android.messaging.util.OsUtil; import com.android.messaging.util.PhoneUtils; public class WidgetConversationListService extends RemoteViewsService { private static final String TAG = LogUtil.BUGLE_WIDGET_TAG; @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { LogUtil.v(TAG, "onGetViewFactory intent: " + intent); } return new WidgetConversationListFactory(getApplicationContext(), intent); } /** * Remote Views Factory for Bugle Widget. */ private static class WidgetConversationListFactory extends BaseWidgetFactory { public WidgetConversationListFactory(Context context, Intent intent) { super(context, intent); } @Override protected Cursor doQuery() { return mContext.getContentResolver().query(MessagingContentProvider.CONVERSATIONS_URI, ConversationListItemData.PROJECTION, ConversationListData.WHERE_NOT_ARCHIVED, null, // selection args ConversationListData.SORT_ORDER); } /** * @return the {@link RemoteViews} for a specific position in the list. */ @Override public RemoteViews getViewAt(int position) { if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { LogUtil.v(TAG, "getViewAt position: " + position); } synchronized (sWidgetLock) { // "View more conversations" view. if (mCursor == null || (mShouldShowViewMore && position >= getItemCount())) { return getViewMoreItemsView(); } if (!mCursor.moveToPosition(position)) { // If we ever fail to move to a position, return the "View More conversations" // view. LogUtil.w(TAG, "Failed to move to position: " + position); return getViewMoreItemsView(); } final ConversationListItemData conv = new ConversationListItemData(); conv.bind(mCursor); // Inflate and fill out the remote view final RemoteViews remoteViews = new RemoteViews( mContext.getPackageName(), R.layout.widget_conversation_list_item); final boolean hasUnreadMessages = !conv.getIsRead(); final Resources resources = mContext.getResources(); final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp(); final String timeStamp = conv.getIsSendRequested() ? resources.getString(R.string.message_status_sending) : Dates.getWidgetTimeString(conv.getTimestamp(), true /*abbreviated*/) .toString(); // Date/Timestamp or Sending or Error state -- all shown in the date item remoteViews.setTextViewText(R.id.date, boldifyIfUnread(timeStamp, hasUnreadMessages)); // From remoteViews.setTextViewText(R.id.from, boldifyIfUnread(conv.getName(), hasUnreadMessages)); // Notifications turned off mini-bell icon remoteViews.setViewVisibility(R.id.conversation_notification_bell, conv.getNotificationEnabled() ? View.GONE : View.VISIBLE); // On click intent. final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext, conv.getConversationId(), null /* draft */); remoteViews.setOnClickFillInIntent(R.id.widget_conversation_list_item, intent); // Avatar boolean includeAvatar; if (OsUtil.isAtLeastJB()) { final Bundle options = mAppWidgetManager.getAppWidgetOptions(mAppWidgetId); if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { LogUtil.v(TAG, "getViewAt BugleWidgetProvider.WIDGET_SIZE_KEY: " + options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY)); } includeAvatar = options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY) == BugleWidgetProvider.SIZE_LARGE; } else { includeAvatar = true;; } // Show the avatar when grande size, otherwise hide it. remoteViews.setViewVisibility(R.id.avatarView, includeAvatar ? View.VISIBLE : View.GONE); Uri iconUri = null; if (conv.getIcon() != null) { iconUri = Uri.parse(conv.getIcon()); } remoteViews.setImageViewBitmap(R.id.avatarView, includeAvatar ? getAvatarBitmap(iconUri) : null); // Error // Only show the fail icon if it is not a group conversation. // And also require that we be the default sms app. final boolean showError = conv.getIsFailedStatus() && !conv.getIsGroup() && isDefaultSmsApp; final boolean showDraft = conv.getShowDraft() && isDefaultSmsApp; remoteViews.setViewVisibility(R.id.conversation_failed_status_icon, showError && includeAvatar ? View.VISIBLE : View.GONE); if (showError || showDraft) { remoteViews.setViewVisibility(R.id.snippet, View.GONE); remoteViews.setViewVisibility(R.id.errorBlock, View.VISIBLE); remoteViews.setTextViewText(R.id.errorSnippet, getSnippetText(conv)); if (showDraft) { // Show italicized "Draft" on third line final String text = resources.getString( R.string.conversation_list_item_view_draft_message); SpannableStringBuilder builder = new SpannableStringBuilder(text); builder.setSpan(new StyleSpan(Typeface.ITALIC), 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); builder.setSpan(new ForegroundColorSpan( resources.getColor(R.color.widget_text_color)), 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); remoteViews.setTextViewText(R.id.errorText, builder); } else { // Show error message on third line int failureMessageId = R.string.message_status_download_failed; if (conv.getIsMessageTypeOutgoing()) { failureMessageId = MmsUtils.mapRawStatusToErrorResourceId( conv.getMessageStatus(), conv.getMessageRawTelephonyStatus()); } remoteViews.setTextViewText(R.id.errorText, resources.getString(failureMessageId)); } } else { remoteViews.setViewVisibility(R.id.errorBlock, View.GONE); remoteViews.setViewVisibility(R.id.snippet, View.VISIBLE); remoteViews.setTextViewText(R.id.snippet, boldifyIfUnread(getSnippetText(conv), hasUnreadMessages)); } // Set the accessibility TalkBack text remoteViews.setContentDescription(R.id.widget_conversation_list_item, ConversationListItemView.buildContentDescription(mContext.getResources(), conv, new TextPaint())); return remoteViews; } } private String getSnippetText(final ConversationListItemData conv) { String snippetText = conv.getShowDraft() ? conv.getDraftSnippetText() : conv.getSnippetText(); final String previewContentType = conv.getShowDraft() ? conv.getDraftPreviewContentType() : conv.getPreviewContentType(); if (TextUtils.isEmpty(snippetText)) { Resources resources = mContext.getResources(); // Use the attachment type as a snippet so the preview doesn't look odd if (ContentType.isAudioType(previewContentType)) { snippetText = resources.getString( R.string.conversation_list_snippet_audio_clip); } else if (ContentType.isImageType(previewContentType)) { snippetText = resources.getString(R.string.conversation_list_snippet_picture); } else if (ContentType.isVideoType(previewContentType)) { snippetText = resources.getString(R.string.conversation_list_snippet_video); } else if (ContentType.isVCardType(previewContentType)) { snippetText = resources.getString(R.string.conversation_list_snippet_vcard); } } return snippetText; } /** * @return the "View more conversations" view. When the user taps this item, they're * taken to the Bugle's conversation list. */ @Override protected RemoteViews getViewMoreItemsView() { if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { LogUtil.v(TAG, "getViewMoreItemsView"); } final RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading); view.setTextViewText( R.id.loading_text, mContext.getText(R.string.view_more_conversations)); // Tapping this "More conversations" item should take us to the ConversationList. // However, the list view is primed with an intent to go to the Conversation activity. // Each normal conversation list item sets the fill-in intent with the // ConversationId for that particular conversation. In other words, the only place // we can go is the ConversationActivity. We add an extra here to tell the // ConversationActivity to really take us to the ConversationListActivity. final Intent intent = new Intent(); intent.putExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, true); view.setOnClickFillInIntent(R.id.widget_loading, intent); return view; } @Override public RemoteViews getLoadingView() { RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading); view.setTextViewText( R.id.loading_text, mContext.getText(R.string.loading_conversations)); return view; } @Override public int getViewTypeCount() { return 2; } @Override protected int getMainLayoutId() { return R.layout.widget_conversation_list; } } }