/*
* 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);
}
}
}