/* * Copyright (C) 2012 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.mms.widget; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.provider.Telephony.Threads; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; import android.text.style.TextAppearanceSpan; import android.util.Log; import android.view.View; import android.widget.RemoteViews; import android.widget.RemoteViewsService; import com.android.mms.LogTag; import com.android.mms.R; import com.android.mms.data.Contact; import com.android.mms.data.Conversation; import com.android.mms.ui.ConversationList; import com.android.mms.ui.ConversationListItem; import com.android.mms.ui.MessageUtils; import com.android.mms.util.SmileyParser; public class MmsWidgetService extends RemoteViewsService { private static final String TAG = "MmsWidgetService"; /** * Lock to avoid race condition between widgets. */ private static final Object sWidgetLock = new Object(); @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "onGetViewFactory intent: " + intent); } return new MmsFactory(getApplicationContext(), intent); } /** * Remote Views Factory for Mms Widget. */ private static class MmsFactory implements RemoteViewsService.RemoteViewsFactory, Contact.UpdateListener { private static final int MAX_CONVERSATIONS_COUNT = 25; private final Context mContext; private final int mAppWidgetId; private boolean mShouldShowViewMore; private Cursor mConversationCursor; private int mUnreadConvCount; private final AppWidgetManager mAppWidgetManager; // Static colors private static int SUBJECT_TEXT_COLOR_READ; private static int SUBJECT_TEXT_COLOR_UNREAD; private static int SENDERS_TEXT_COLOR_READ; private static int SENDERS_TEXT_COLOR_UNREAD; public MmsFactory(Context context, Intent intent) { mContext = context; mAppWidgetId = intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); mAppWidgetManager = AppWidgetManager.getInstance(context); if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "MmsFactory intent: " + intent + "widget id: " + mAppWidgetId); } // Initialize colors Resources res = context.getResources(); SENDERS_TEXT_COLOR_READ = res.getColor(R.color.widget_sender_text_color_read); SENDERS_TEXT_COLOR_UNREAD = res.getColor(R.color.widget_sender_text_color_unread); SUBJECT_TEXT_COLOR_READ = res.getColor(R.color.widget_subject_text_color_read); SUBJECT_TEXT_COLOR_UNREAD = res.getColor(R.color.widget_subject_text_color_unread); } @Override public void onCreate() { if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "onCreate"); } Contact.addListener(this); } @Override public void onDestroy() { if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "onDestroy"); } synchronized (sWidgetLock) { if (mConversationCursor != null && !mConversationCursor.isClosed()) { mConversationCursor.close(); mConversationCursor = null; } Contact.removeListener(this); } } @Override public void onDataSetChanged() { if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "onDataSetChanged"); } synchronized (sWidgetLock) { if (mConversationCursor != null) { mConversationCursor.close(); mConversationCursor = null; } mConversationCursor = queryAllConversations(); mUnreadConvCount = queryUnreadCount(); onLoadComplete(); } } private Cursor queryAllConversations() { return mContext.getContentResolver().query( Conversation.sAllThreadsUri, Conversation.ALL_THREADS_PROJECTION, null, null, null); } private int queryUnreadCount() { Cursor cursor = null; int unreadCount = 0; try { cursor = mContext.getContentResolver().query( Conversation.sAllThreadsUri, Conversation.ALL_THREADS_PROJECTION, Threads.READ + "=0", null, null); if (cursor != null) { unreadCount = cursor.getCount(); } } finally { if (cursor != null) { cursor.close(); } } return unreadCount; } /** * Returns the number of items should be shown in the widget list. This method also updates * the boolean that indicates whether the "show more" item should be shown. * @return the number of items to be displayed in the list. */ @Override public int getCount() { if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "getCount"); } synchronized (sWidgetLock) { if (mConversationCursor == null) { return 0; } final int count = getConversationCount(); mShouldShowViewMore = count < mConversationCursor.getCount(); return count + (mShouldShowViewMore ? 1 : 0); } } /** * Returns the number of conversations that should be shown in the widget. This method * doesn't update the boolean that indicates that the "show more" item should be included * in the list. * @return */ private int getConversationCount() { if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "getConversationCount"); } synchronized (sWidgetLock) { return Math.min(mConversationCursor.getCount(), MAX_CONVERSATIONS_COUNT); } } /* * Add color to a given text */ private SpannableStringBuilder addColor(CharSequence text, int color) { SpannableStringBuilder builder = new SpannableStringBuilder(text); if (color != 0) { builder.setSpan(new ForegroundColorSpan(color), 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } return builder; } /** * @return the {@link RemoteViews} for a specific position in the list. */ @Override public RemoteViews getViewAt(int position) { if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "getViewAt position: " + position); } synchronized (sWidgetLock) { // "View more conversations" view. if (mConversationCursor == null || (mShouldShowViewMore && position >= getConversationCount())) { return getViewMoreConversationsView(); } if (!mConversationCursor.moveToPosition(position)) { // If we ever fail to move to a position, return the "View More conversations" // view. Log.w(TAG, "Failed to move to position: " + position); return getViewMoreConversationsView(); } Conversation conv = Conversation.from(mContext, mConversationCursor); // Inflate and fill out the remote view RemoteViews remoteViews = new RemoteViews( mContext.getPackageName(), R.layout.widget_conversation); if (conv.hasUnreadMessages()) { remoteViews.setViewVisibility(R.id.widget_unread_background, View.VISIBLE); remoteViews.setViewVisibility(R.id.widget_read_background, View.GONE); } else { remoteViews.setViewVisibility(R.id.widget_unread_background, View.GONE); remoteViews.setViewVisibility(R.id.widget_read_background, View.VISIBLE); } boolean hasAttachment = conv.hasAttachment(); remoteViews.setViewVisibility(R.id.attachment, hasAttachment ? View.VISIBLE : View.GONE); // Date remoteViews.setTextViewText(R.id.date, addColor(MessageUtils.formatTimeStampString(mContext, conv.getDate()), conv.hasUnreadMessages() ? SUBJECT_TEXT_COLOR_UNREAD : SUBJECT_TEXT_COLOR_READ)); // From int color = conv.hasUnreadMessages() ? SENDERS_TEXT_COLOR_UNREAD : SENDERS_TEXT_COLOR_READ; SpannableStringBuilder from = addColor(conv.getRecipients().formatNames(", "), color); if (conv.hasDraft()) { from.append(mContext.getResources().getString(R.string.draft_separator)); int before = from.length(); from.append(mContext.getResources().getString(R.string.has_draft)); from.setSpan(new TextAppearanceSpan(mContext, android.R.style.TextAppearance_Small, color), before, from.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); from.setSpan(new ForegroundColorSpan( mContext.getResources().getColor(R.drawable.text_color_red)), before, from.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } // Unread messages are shown in bold if (conv.hasUnreadMessages()) { from.setSpan(ConversationListItem.STYLE_BOLD, 0, from.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } remoteViews.setTextViewText(R.id.from, from); // Subject // TODO: the SmileyParser inserts image spans but they don't seem to make it // into the remote view. SmileyParser parser = SmileyParser.getInstance(); remoteViews.setTextViewText(R.id.subject, addColor(parser.addSmileySpans(conv.getSnippet()), conv.hasUnreadMessages() ? SUBJECT_TEXT_COLOR_UNREAD : SUBJECT_TEXT_COLOR_READ)); // On click intent. Intent clickIntent = new Intent(Intent.ACTION_VIEW); clickIntent.setType("vnd.android-dir/mms-sms"); clickIntent.putExtra("thread_id", conv.getThreadId()); remoteViews.setOnClickFillInIntent(R.id.widget_conversation, clickIntent); return remoteViews; } } /** * @return the "View more conversations" view. When the user taps this item, they're * taken to the messaging app's conversation list. */ private RemoteViews getViewMoreConversationsView() { if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "getViewMoreConversationsView"); } RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading); view.setTextViewText( R.id.loading_text, mContext.getText(R.string.view_more_conversations)); view.setOnClickFillInIntent(R.id.widget_loading, new Intent(mContext, ConversationList.class)); 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 public long getItemId(int position) { return position; } @Override public boolean hasStableIds() { return true; } private void onLoadComplete() { if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "onLoadComplete"); } RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), R.layout.widget); remoteViews.setViewVisibility(R.id.widget_unread_count, mUnreadConvCount > 0 ? View.VISIBLE : View.GONE); if (mUnreadConvCount > 0) { remoteViews.setTextViewText( R.id.widget_unread_count, Integer.toString(mUnreadConvCount)); } mAppWidgetManager.partiallyUpdateAppWidget(mAppWidgetId, remoteViews); } public void onUpdate(Contact updated) { if (Log.isLoggable(LogTag.WIDGET, Log.VERBOSE)) { Log.v(TAG, "onUpdate from Contact: " + updated); } mAppWidgetManager.notifyAppWidgetViewDataChanged(mAppWidgetId, R.id.conversation_list); } } }