| /* |
| * 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.app.PendingIntent; |
| 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"); |
| } |
| |
| 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)); |
| PendingIntent pendingIntent = |
| PendingIntent.getActivity(mContext, 0, new Intent(mContext, |
| ConversationList.class), |
| PendingIntent.FLAG_UPDATE_CURRENT); |
| view.setOnClickPendingIntent(R.id.widget_loading, pendingIntent); |
| 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); |
| } |
| |
| } |
| } |