Revert "Initial checkin of AOSP Messaging app."

This reverts commit 461a34b466.

Change-Id: Iac4ca77eeaa94989e91dead49a7959c905bd3078
This commit is contained in:
Mike Dodd
2015-08-12 18:48:39 +00:00
parent 461a34b466
commit ef8c7abbcf
1645 changed files with 0 additions and 186271 deletions
@@ -1,232 +0,0 @@
/*
* 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.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Binder;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
import com.android.messaging.R;
import com.android.messaging.datamodel.media.AvatarGroupRequestDescriptor;
import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
import com.android.messaging.datamodel.media.ImageRequestDescriptor;
import com.android.messaging.datamodel.media.ImageResource;
import com.android.messaging.datamodel.media.MediaRequest;
import com.android.messaging.datamodel.media.MediaResourceManager;
import com.android.messaging.util.AvatarUriUtil;
import com.android.messaging.util.LogUtil;
/**
* Remote Views Factory for Bugle Widget.
*/
abstract class BaseWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
protected static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
protected static final int MAX_ITEMS_TO_SHOW = 25;
/**
* Lock to avoid race condition between widgets.
*/
protected static final Object sWidgetLock = new Object();
protected final Context mContext;
protected final int mAppWidgetId;
protected boolean mShouldShowViewMore;
protected Cursor mCursor;
protected final AppWidgetManager mAppWidgetManager;
protected int mIconSize;
protected ImageResource mAvatarResource;
public BaseWidgetFactory(Context context, Intent intent) {
mContext = context;
mAppWidgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
mAppWidgetManager = AppWidgetManager.getInstance(context);
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "BaseWidgetFactory intent: " + intent + "widget id: " + mAppWidgetId);
}
mIconSize = (int) context.getResources()
.getDimension(R.dimen.contact_icon_view_normal_size);
}
@Override
public void onCreate() {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "onCreate");
}
}
@Override
public void onDestroy() {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "onDestroy");
}
synchronized (sWidgetLock) {
if (mCursor != null && !mCursor.isClosed()) {
mCursor.close();
mCursor = null;
}
}
}
@Override
public void onDataSetChanged() {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "onDataSetChanged");
}
synchronized (sWidgetLock) {
if (mCursor != null) {
mCursor.close();
mCursor = null;
}
final long token = Binder.clearCallingIdentity();
try {
mCursor = doQuery();
onLoadComplete();
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
protected abstract Cursor doQuery();
/**
* Returns the number of items that 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() {
synchronized (sWidgetLock) {
if (mCursor == null) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "getCount: 0");
}
return 0;
}
final int count = getItemCount();
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "getCount: " + count);
}
mShouldShowViewMore = count < mCursor.getCount();
return count + (mShouldShowViewMore ? 1 : 0);
}
}
/**
* Returns the number of messages that should be shown in the widget. This method
* doesn't update the boolean that indicates whether the "show more" item should be included
* in the list.
* @return
*/
protected int getItemCount() {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "getItemCount: " + mCursor.getCount());
}
return Math.min(mCursor.getCount(), MAX_ITEMS_TO_SHOW);
}
/*
* Make the given text bold if the item is unread
*/
protected CharSequence boldifyIfUnread(CharSequence text, final boolean unread) {
if (!unread) {
return text;
}
final SpannableStringBuilder builder = new SpannableStringBuilder(text);
builder.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return builder;
}
protected Bitmap getAvatarBitmap(final Uri avatarUri) {
final String avatarType = avatarUri == null ?
null : AvatarUriUtil.getAvatarType(avatarUri);
ImageRequestDescriptor descriptor;
if (AvatarUriUtil.TYPE_GROUP_URI.equals(avatarType)) {
descriptor = new AvatarGroupRequestDescriptor(avatarUri, mIconSize, mIconSize);
} else {
descriptor = new AvatarRequestDescriptor(avatarUri, mIconSize, mIconSize);
}
final MediaRequest<ImageResource> imageRequest =
descriptor.buildSyncMediaRequest(mContext);
final ImageResource imageResource =
MediaResourceManager.get().requestMediaResourceSync(imageRequest);
if (imageResource != null) {
setAvatarResource(imageResource);
return mAvatarResource.getBitmap();
} else {
releaseAvatarResource();
return null;
}
}
/**
* @return the "View more messages" view. When the user taps this item, they're
* taken to the conversation in Bugle.
*/
abstract protected RemoteViews getViewMoreItemsView();
@Override
public boolean hasStableIds() {
return true;
}
@Override
public long getItemId(int position) {
return position;
}
private void onLoadComplete() {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "onLoadComplete");
}
final RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(),
getMainLayoutId());
mAppWidgetManager.partiallyUpdateAppWidget(mAppWidgetId, remoteViews);
}
protected abstract int getMainLayoutId();
private void setAvatarResource(final ImageResource resource) {
if (mAvatarResource != resource) {
// Clear out any information for what is currently used
releaseAvatarResource();
mAvatarResource = resource;
}
}
private void releaseAvatarResource() {
if (mAvatarResource != null) {
mAvatarResource.release();
}
mAvatarResource = null;
}
}
@@ -1,184 +0,0 @@
/*
* 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.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.android.messaging.util.LogUtil;
public abstract class BaseWidgetProvider extends AppWidgetProvider {
protected static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
public static final int WIDGET_CONVERSATION_REQUEST_CODE = 987;
static final String WIDGET_SIZE_KEY = "widgetSizeKey";
public static final int SIZE_LARGE = 0; // undefined == 0, which is the default, large
public static final int SIZE_SMALL = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_PRE_JB = 3;
/**
* Update all widgets in the list
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
for (int i = 0; i < appWidgetIds.length; ++i) {
updateWidget(context, appWidgetIds[i]);
}
}
@Override
public void onReceive(Context context, Intent intent) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "onReceive intent: " + intent + " for " + this.getClass());
}
final String action = intent.getAction();
// The base class AppWidgetProvider's onReceive handles the normal widget intents. Here
// we're looking for an intent sent by our app when it knows a message has
// been sent or received (or a conversation has been read) and is telling the widget it
// needs to update.
if (getAction().equals(action)) {
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context,
this.getClass()));
if (appWidgetIds.length > 0) {
// We need to update all Bugle app widgets on the home screen.
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "onReceive notifyAppWidgetViewDataChanged listId: " +
getListId() + " first widgetId: " + appWidgetIds[0]);
}
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, getListId());
}
} else {
super.onReceive(context, intent);
}
}
protected abstract String getAction();
protected abstract int getListId();
/**
* Update the widget appWidgetId
*/
protected abstract void updateWidget(Context context, int appWidgetId);
private int getWidgetSize(AppWidgetManager appWidgetManager,
int appWidgetId) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize");
}
// Get the dimensions
final Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
// Get min width and height.
final int minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
final int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
// First find out rows and columns based on width provided.
final int rows = getCellsForSize(minHeight);
final int columns = getCellsForSize(minWidth);
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize row: " + rows +
" columns: " + columns);
}
int size = SIZE_MEDIUM;
if (rows == 1) {
size = SIZE_SMALL; // Our widget doesn't let itself get this small. Perhaps in the
// future will add a super-mini widget.
} else if (columns > 3) {
size = SIZE_LARGE;
}
// put the size in the bundle so our service know what size it's dealing with.
final int savedSize = options.getInt(WIDGET_SIZE_KEY);
if (savedSize != size) {
options.putInt(WIDGET_SIZE_KEY, size);
appWidgetManager.updateAppWidgetOptions(appWidgetId, options);
// The size changed. We have to force the widget to rebuild the list.
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, getListId());
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize old size: " + savedSize +
" new size saved: " + size);
}
}
return size;
}
/**
* Returns number of cells needed for given size of the widget.
*
* @param size Widget size in dp.
* @return Size in number of cells.
*/
private static int getCellsForSize(int size) {
// The hardwired sizes in this function come from the hardwired formula found in
// Android's UI guidelines for widget design:
// http://developer.android.com/guide/practices/ui_guidelines/widget_design.html
return (size + 30) / 70;
}
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, Bundle newOptions) {
final int widgetSize = getWidgetSize(appWidgetManager, appWidgetId);
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "BaseWidgetProvider.onAppWidgetOptionsChanged new size: " +
widgetSize);
}
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}
protected void deletePreferences(final int widgetId) {
}
/**
* Remove preferences when deleting widget
*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "BaseWidgetProvider.onDeleted");
}
for (final int widgetId : appWidgetIds) {
deletePreferences(widgetId);
}
}
}
@@ -1,114 +0,0 @@
/*
* 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.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.RemoteViews;
import com.android.messaging.R;
import com.android.messaging.ui.UIIntents;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.OsUtil;
import com.android.messaging.util.SafeAsyncTask;
import com.android.messaging.util.UiUtils;
public class BugleWidgetProvider extends BaseWidgetProvider {
public static final String ACTION_NOTIFY_CONVERSATIONS_CHANGED =
"com.android.Bugle.intent.action.ACTION_NOTIFY_CONVERSATIONS_CHANGED";
public static final int WIDGET_NEW_CONVERSATION_REQUEST_CODE = 986;
/**
* Update the widget appWidgetId
*/
@Override
protected void updateWidget(final Context context, final int appWidgetId) {
if (OsUtil.hasRequiredPermissions()) {
SafeAsyncTask.executeOnThreadPool(new Runnable() {
@Override
public void run() {
rebuildWidget(context, appWidgetId);
}
});
} else {
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId,
UiUtils.getWidgetMissingPermissionView(context));
}
}
@Override
protected String getAction() {
return ACTION_NOTIFY_CONVERSATIONS_CHANGED;
}
@Override
protected int getListId() {
return R.id.conversation_list;
}
public static void rebuildWidget(final Context context, final int appWidgetId) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "BugleWidgetProvider.rebuildWidget appWidgetId: " + appWidgetId);
}
final RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.widget_conversation_list);
PendingIntent clickIntent;
// Launch an intent to avoid ANRs
final Intent intent = new Intent(context, WidgetConversationListService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
remoteViews.setRemoteAdapter(appWidgetId, R.id.conversation_list, intent);
remoteViews.setTextViewText(R.id.widget_label, context.getString(R.string.app_name));
// Open Bugle's app conversation list when click on header
clickIntent = UIIntents.get().getWidgetPendingIntentForConversationListActivity(context);
remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
// On click intent for Compose
clickIntent = UIIntents.get().getWidgetPendingIntentForConversationActivity(context,
null /*conversationId*/, WIDGET_NEW_CONVERSATION_REQUEST_CODE);
remoteViews.setOnClickPendingIntent(R.id.widget_compose, clickIntent);
// On click intent for Conversation
// Note: the template intent has to be a "naked" intent without any extras. It turns out
// that if the template intent does have extras, those particular extras won't get
// replaced by the fill-in intent on each list item.
clickIntent = UIIntents.get().getWidgetPendingIntentForConversationActivity(context,
null /*conversationId*/, WIDGET_CONVERSATION_REQUEST_CODE);
remoteViews.setPendingIntentTemplate(R.id.conversation_list, clickIntent);
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews);
}
/*
* notifyDatasetChanged call when the conversation list changes so the Bugle widget will
* update and reflect the changes
*/
public static void notifyConversationListChanged(final Context context) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "notifyConversationListChanged");
}
final Intent intent = new Intent(ACTION_NOTIFY_CONVERSATIONS_CHANGED);
context.sendBroadcast(intent);
}
}
@@ -1,281 +0,0 @@
/*
* 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() &&
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;
}
}
}
@@ -1,316 +0,0 @@
/*
* 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.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Looper;
import android.text.TextUtils;
import android.view.View;
import android.widget.RemoteViews;
import com.android.messaging.R;
import com.android.messaging.datamodel.MessagingContentProvider;
import com.android.messaging.datamodel.data.ConversationListItemData;
import com.android.messaging.ui.UIIntents;
import com.android.messaging.ui.WidgetPickConversationActivity;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.OsUtil;
import com.android.messaging.util.SafeAsyncTask;
import com.android.messaging.util.UiUtils;
public class WidgetConversationProvider extends BaseWidgetProvider {
public static final String ACTION_NOTIFY_MESSAGES_CHANGED =
"com.android.Bugle.intent.action.ACTION_NOTIFY_MESSAGES_CHANGED";
public static final int WIDGET_CONVERSATION_TEMPLATE_REQUEST_CODE = 1985;
public static final int WIDGET_CONVERSATION_REPLY_CODE = 1987;
// Intent extras
public static final String UI_INTENT_EXTRA_RECIPIENT = "recipient";
public static final String UI_INTENT_EXTRA_ICON = "icon";
/**
* Update the widget appWidgetId
*/
@Override
protected void updateWidget(final Context context, final int appWidgetId) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "updateWidget appWidgetId: " + appWidgetId);
}
if (OsUtil.hasRequiredPermissions()) {
rebuildWidget(context, appWidgetId);
} else {
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId,
UiUtils.getWidgetMissingPermissionView(context));
}
}
@Override
protected String getAction() {
return ACTION_NOTIFY_MESSAGES_CHANGED;
}
@Override
protected int getListId() {
return R.id.message_list;
}
public static void rebuildWidget(final Context context, final int appWidgetId) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "WidgetConversationProvider.rebuildWidget appWidgetId: " + appWidgetId);
}
final RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.widget_conversation);
PendingIntent clickIntent;
final UIIntents uiIntents = UIIntents.get();
if (!isWidgetConfigured(appWidgetId)) {
// Widget has not been configured yet. Hide the normal UI elements and show the
// configuration view instead.
remoteViews.setViewVisibility(R.id.widget_label, View.GONE);
remoteViews.setViewVisibility(R.id.message_list, View.GONE);
remoteViews.setViewVisibility(R.id.launcher_icon, View.VISIBLE);
remoteViews.setViewVisibility(R.id.widget_configuration, View.VISIBLE);
remoteViews.setOnClickPendingIntent(R.id.widget_configuration,
uiIntents.getWidgetPendingIntentForConfigurationActivity(context, appWidgetId));
// On click intent for Goto Conversation List
clickIntent = uiIntents.getWidgetPendingIntentForConversationListActivity(context);
remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "WidgetConversationProvider.rebuildWidget appWidgetId: " +
appWidgetId + " going into configure state");
}
} else {
remoteViews.setViewVisibility(R.id.widget_label, View.VISIBLE);
remoteViews.setViewVisibility(R.id.message_list, View.VISIBLE);
remoteViews.setViewVisibility(R.id.launcher_icon, View.GONE);
remoteViews.setViewVisibility(R.id.widget_configuration, View.GONE);
final String conversationId =
WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
final boolean isMainThread = Looper.myLooper() == Looper.getMainLooper();
// If we're running on the UI thread, we can't do the DB access needed to get the
// conversation data. We'll do excute this again off of the UI thread.
final ConversationListItemData convData = isMainThread ?
null : getConversationData(context, conversationId);
// Launch an intent to avoid ANRs
final Intent intent = new Intent(context, WidgetConversationService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
remoteViews.setRemoteAdapter(appWidgetId, R.id.message_list, intent);
remoteViews.setTextViewText(R.id.widget_label, convData != null ?
convData.getName() : context.getString(R.string.app_name));
// On click intent for Goto Conversation List
clickIntent = uiIntents.getWidgetPendingIntentForConversationListActivity(context);
remoteViews.setOnClickPendingIntent(R.id.widget_goto_conversation_list, clickIntent);
// Open the conversation when click on header
clickIntent = uiIntents.getWidgetPendingIntentForConversationActivity(context,
conversationId, WIDGET_CONVERSATION_REQUEST_CODE);
remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
// On click intent for Conversation
// Note: the template intent has to be a "naked" intent without any extras. It turns out
// that if the template intent does have extras, those particular extras won't get
// replaced by the fill-in intent on each list item.
clickIntent = uiIntents.getWidgetPendingIntentForConversationActivity(context,
conversationId, WIDGET_CONVERSATION_TEMPLATE_REQUEST_CODE);
remoteViews.setPendingIntentTemplate(R.id.message_list, clickIntent);
if (isMainThread) {
// We're running on the UI thread and we couldn't update all the parts of the
// widget dependent on ConversationListItemData. However, we have to update
// the widget regardless, even with those missing pieces. Here we update the
// widget again in the background.
SafeAsyncTask.executeOnThreadPool(new Runnable() {
@Override
public void run() {
rebuildWidget(context, appWidgetId);
}
});
}
}
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews);
}
/*
* notifyMessagesChanged called when the conversation changes so the widget will
* update and reflect the changes
*/
public static void notifyMessagesChanged(final Context context, final String conversationId) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "notifyMessagesChanged");
}
final Intent intent = new Intent(ACTION_NOTIFY_MESSAGES_CHANGED);
intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
context.sendBroadcast(intent);
}
/*
* notifyConversationDeleted is called when a conversation is deleted. Look through all the
* widgets and if they're displaying that conversation, force the widget into its
* configuration state.
*/
public static void notifyConversationDeleted(final Context context,
final String conversationId) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "notifyConversationDeleted convId: " + conversationId);
}
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (final int appWidgetId : appWidgetManager.getAppWidgetIds(new ComponentName(context,
WidgetConversationProvider.class))) {
// Retrieve the persisted information for this widget from preferences.
final String widgetConvId =
WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
if (widgetConvId == null || widgetConvId.equals(conversationId)) {
if (widgetConvId != null) {
WidgetPickConversationActivity.deleteConversationIdPref(appWidgetId);
}
rebuildWidget(context, appWidgetId);
}
}
}
/*
* notifyConversationRenamed is called when a conversation is renamed. Look through all the
* widgets and if they're displaying that conversation, force the widget to rebuild itself
*/
public static void notifyConversationRenamed(final Context context,
final String conversationId) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "notifyConversationRenamed convId: " + conversationId);
}
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (final int appWidgetId : appWidgetManager.getAppWidgetIds(new ComponentName(context,
WidgetConversationProvider.class))) {
// Retrieve the persisted information for this widget from preferences.
final String widgetConvId =
WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
if (widgetConvId != null && widgetConvId.equals(conversationId)) {
rebuildWidget(context, appWidgetId);
}
}
}
@Override
public void onReceive(final Context context, final Intent intent) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "WidgetConversationProvider onReceive intent: " + intent);
}
final String action = intent.getAction();
// The base class AppWidgetProvider's onReceive handles the normal widget intents. Here
// we're looking for an intent sent by our app when it knows a message has
// been sent or received (or a conversation has been read) and is telling the widget it
// needs to update.
if (getAction().equals(action)) {
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context,
this.getClass()));
if (appWidgetIds.length == 0) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "WidgetConversationProvider onReceive no widget ids");
}
return;
}
// Normally the conversation id points to a specific conversation and we only update
// widgets looking at that conversation. When the conversation id is null, that means
// there's been a massive change (such as the initial import) and we need to update
// every conversation widget.
final String conversationId = intent.getExtras()
.getString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
// Only update the widgets that match the conversation id that changed.
for (final int widgetId : appWidgetIds) {
// Retrieve the persisted information for this widget from preferences.
final String widgetConvId =
WidgetPickConversationActivity.getConversationIdPref(widgetId);
if (conversationId == null || TextUtils.equals(conversationId, widgetConvId)) {
// Update the list portion (i.e. the message list) of the widget
appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, getListId());
}
}
} else {
super.onReceive(context, intent);
}
}
private static ConversationListItemData getConversationData(final Context context,
final String conversationId) {
if (TextUtils.isEmpty(conversationId)) {
return null;
}
final Uri uri = MessagingContentProvider.buildConversationMetadataUri(conversationId);
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri,
ConversationListItemData.PROJECTION,
null, // selection
null, // selection args
null); // sort order
if (cursor != null && cursor.getCount() > 0) {
final ConversationListItemData conv = new ConversationListItemData();
cursor.moveToFirst();
conv.bind(cursor);
return conv;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
@Override
protected void deletePreferences(final int widgetId) {
WidgetPickConversationActivity.deleteConversationIdPref(widgetId);
}
/**
* When this widget is created, it's created for a particular conversation and that
* ConversationId is stored in shared prefs. If the associated conversation is deleted,
* the widget doesn't get deleted. Instead, it goes into a "tap to configure" state. This
* function determines whether the widget has been configured and has an associated
* ConversationId.
*/
public static boolean isWidgetConfigured(final int appWidgetId) {
final String conversationId =
WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
return !TextUtils.isEmpty(conversationId);
}
}
@@ -1,521 +0,0 @@
/*
* 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.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Formatter;
import android.text.style.ForegroundColorSpan;
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.ConversationMessageData;
import com.android.messaging.datamodel.data.MessageData;
import com.android.messaging.datamodel.data.MessagePartData;
import com.android.messaging.datamodel.media.ImageResource;
import com.android.messaging.datamodel.media.MediaRequest;
import com.android.messaging.datamodel.media.MediaResourceManager;
import com.android.messaging.datamodel.media.MessagePartImageRequestDescriptor;
import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor;
import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
import com.android.messaging.datamodel.media.VideoThumbnailRequest;
import com.android.messaging.sms.MmsUtils;
import com.android.messaging.ui.UIIntents;
import com.android.messaging.util.AvatarUriUtil;
import com.android.messaging.util.Dates;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.OsUtil;
import com.android.messaging.util.PhoneUtils;
import java.util.List;
public class WidgetConversationService extends RemoteViewsService {
private static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
private static final int IMAGE_ATTACHMENT_SIZE = 400;
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "onGetViewFactory intent: " + intent);
}
return new WidgetConversationFactory(getApplicationContext(), intent);
}
/**
* Remote Views Factory for the conversation widget.
*/
private static class WidgetConversationFactory extends BaseWidgetFactory {
private ImageResource mImageResource;
private String mConversationId;
public WidgetConversationFactory(Context context, Intent intent) {
super(context, intent);
mConversationId = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "BugleFactory intent: " + intent + "widget id: " + mAppWidgetId);
}
mIconSize = (int) context.getResources()
.getDimension(R.dimen.contact_icon_view_normal_size);
}
@Override
public void onCreate() {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "onCreate");
}
super.onCreate();
// If the conversation for this widget has been removed, we want to update the widget to
// "Tap to configure" mode.
if (!WidgetConversationProvider.isWidgetConfigured(mAppWidgetId)) {
WidgetConversationProvider.rebuildWidget(mContext, mAppWidgetId);
}
}
@Override
protected Cursor doQuery() {
if (TextUtils.isEmpty(mConversationId)) {
LogUtil.w(TAG, "doQuery no conversation id");
return null;
}
final Uri uri = MessagingContentProvider.buildConversationMessagesUri(mConversationId);
if (uri != null) {
LogUtil.w(TAG, "doQuery uri: " + uri.toString());
}
return mContext.getContentResolver().query(uri,
ConversationMessageData.getProjection(),
null, // where
null, // selection args
null // sort order
);
}
/**
* @return the {@link RemoteViews} for a specific position in the list.
*/
@Override
public RemoteViews getViewAt(final int originalPosition) {
synchronized (sWidgetLock) {
// "View more messages" view.
if (mCursor == null
|| (mShouldShowViewMore && originalPosition == 0)) {
return getViewMoreItemsView();
}
// The message cursor is in reverse order for performance reasons.
final int position = getCount() - originalPosition - 1;
if (!mCursor.moveToPosition(position)) {
// If we ever fail to move to a position, return the "View More messages"
// view.
LogUtil.w(TAG, "Failed to move to position: " + position);
return getViewMoreItemsView();
}
final ConversationMessageData message = new ConversationMessageData();
message.bind(mCursor);
// Inflate and fill out the remote view
final RemoteViews remoteViews = new RemoteViews(
mContext.getPackageName(), message.getIsIncoming() ?
R.layout.widget_message_item_incoming :
R.layout.widget_message_item_outgoing);
final boolean hasUnreadMessages = false; //!message.getIsRead();
// Date
remoteViews.setTextViewText(R.id.date, boldifyIfUnread(
Dates.getWidgetTimeString(message.getReceivedTimeStamp(),
false /*abbreviated*/),
hasUnreadMessages));
// On click intent.
final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext,
mConversationId, null /* draft */);
// Attachments
int attachmentStringId = 0;
remoteViews.setViewVisibility(R.id.attachmentFrame, View.GONE);
int scrollToPosition = originalPosition;
final int cursorCount = mCursor.getCount();
if (cursorCount > MAX_ITEMS_TO_SHOW) {
scrollToPosition += cursorCount - MAX_ITEMS_TO_SHOW;
}
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "getViewAt position: " + originalPosition +
" computed position: " + position +
" scrollToPosition: " + scrollToPosition +
" cursorCount: " + cursorCount +
" MAX_ITEMS_TO_SHOW: " + MAX_ITEMS_TO_SHOW);
}
intent.putExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, scrollToPosition);
if (message.hasAttachments()) {
final List<MessagePartData> attachments = message.getAttachments();
for (MessagePartData part : attachments) {
final boolean videoWithThumbnail = part.isVideo()
&& (VideoThumbnailRequest.shouldShowIncomingVideoThumbnails()
|| !message.getIsIncoming());
if (part.isImage() || videoWithThumbnail) {
final Uri uri = part.getContentUri();
remoteViews.setViewVisibility(R.id.attachmentFrame, View.VISIBLE);
remoteViews.setViewVisibility(R.id.playButton, part.isVideo() ?
View.VISIBLE : View.GONE);
remoteViews.setImageViewBitmap(R.id.attachment,
getAttachmentBitmap(part));
intent.putExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_URI ,
uri.toString());
intent.putExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_TYPE ,
part.getContentType());
break;
} else if (part.isVideo()) {
attachmentStringId = R.string.conversation_list_snippet_video;
break;
}
if (part.isAudio()) {
attachmentStringId = R.string.conversation_list_snippet_audio_clip;
break;
}
if (part.isVCard()) {
attachmentStringId = R.string.conversation_list_snippet_vcard;
break;
}
}
}
remoteViews.setOnClickFillInIntent(message.getIsIncoming() ?
R.id.widget_message_item_incoming :
R.id.widget_message_item_outgoing,
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 (and shadow) when grande size, otherwise hide it.
remoteViews.setViewVisibility(R.id.avatarView, includeAvatar ?
View.VISIBLE : View.GONE);
remoteViews.setViewVisibility(R.id.avatarShadow, includeAvatar ?
View.VISIBLE : View.GONE);
final Uri avatarUri = AvatarUriUtil.createAvatarUri(
message.getSenderProfilePhotoUri(),
message.getSenderFullName(),
message.getSenderNormalizedDestination(),
message.getSenderContactLookupKey());
remoteViews.setImageViewBitmap(R.id.avatarView, includeAvatar ?
getAvatarBitmap(avatarUri) : null);
String text = message.getText();
if (attachmentStringId != 0) {
final String attachment = mContext.getString(attachmentStringId);
if (!TextUtils.isEmpty(text)) {
text += '\n' + attachment;
} else {
text = attachment;
}
}
remoteViews.setViewVisibility(R.id.message, View.VISIBLE);
updateViewContent(text, message, remoteViews);
return remoteViews;
}
}
// updateViewContent figures out what to show in the message and date fields based on
// the message status. This code came from ConversationMessageView.updateViewContent, but
// had to be simplified to work with our simple widget list item.
// updateViewContent also builds the accessibility content description for the list item.
private void updateViewContent(final String messageText,
final ConversationMessageData message,
final RemoteViews remoteViews) {
int titleResId = -1;
int statusResId = -1;
boolean showInRed = false;
String statusText = null;
switch(message.getStatus()) {
case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
titleResId = R.string.message_title_downloading;
statusResId = R.string.message_status_downloading;
break;
case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
if (!OsUtil.isSecondaryUser()) {
titleResId = R.string.message_title_manual_download;
statusResId = R.string.message_status_download;
}
break;
case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
if (!OsUtil.isSecondaryUser()) {
titleResId = R.string.message_title_download_failed;
statusResId = R.string.message_status_download_error;
showInRed = true;
}
break;
case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
if (!OsUtil.isSecondaryUser()) {
titleResId = R.string.message_title_download_failed;
statusResId = R.string.message_status_download;
showInRed = true;
}
break;
case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
statusResId = R.string.message_status_sending;
break;
case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
statusResId = R.string.message_status_send_retrying;
break;
case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
statusResId = R.string.message_status_send_failed_emergency_number;
showInRed = true;
break;
case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
// don't show the error state unless we're the default sms app
if (PhoneUtils.getDefault().isDefaultSmsApp()) {
statusResId = MmsUtils.mapRawStatusToErrorResourceId(
message.getStatus(), message.getRawTelephonyStatus());
showInRed = true;
break;
}
// FALL THROUGH HERE
case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
case MessageData.BUGLE_STATUS_INCOMING_COMPLETE:
default:
if (!message.getCanClusterWithNextMessage()) {
statusText = Dates.getWidgetTimeString(message.getReceivedTimeStamp(),
false /*abbreviated*/).toString();
}
break;
}
// Build the content description while we're populating the various fields.
final StringBuilder description = new StringBuilder();
final String separator = mContext.getString(R.string.enumeration_comma);
// Sender information
final boolean hasPlainTextMessage = !(TextUtils.isEmpty(message.getText()));
if (message.getIsIncoming()) {
int senderResId = hasPlainTextMessage
? R.string.incoming_text_sender_content_description
: R.string.incoming_sender_content_description;
description.append(mContext.getString(senderResId, message.getSenderDisplayName()));
} else {
int senderResId = hasPlainTextMessage
? R.string.outgoing_text_sender_content_description
: R.string.outgoing_sender_content_description;
description.append(mContext.getString(senderResId));
}
final boolean titleVisible = (titleResId >= 0);
if (titleVisible) {
final String titleText = mContext.getString(titleResId);
remoteViews.setTextViewText(R.id.message, titleText);
final String mmsInfoText = mContext.getString(
R.string.mms_info,
Formatter.formatFileSize(mContext, message.getSmsMessageSize()),
DateUtils.formatDateTime(
mContext,
message.getMmsExpiry(),
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME |
DateUtils.FORMAT_NUMERIC_DATE |
DateUtils.FORMAT_NO_YEAR));
remoteViews.setTextViewText(R.id.date, mmsInfoText);
description.append(separator);
description.append(mmsInfoText);
} else if (!TextUtils.isEmpty(messageText)) {
remoteViews.setTextViewText(R.id.message, messageText);
description.append(separator);
description.append(messageText);
} else {
remoteViews.setViewVisibility(R.id.message, View.GONE);
}
final String subjectText = MmsUtils.cleanseMmsSubject(mContext.getResources(),
message.getMmsSubject());
if (!TextUtils.isEmpty(subjectText)) {
description.append(separator);
description.append(subjectText);
}
if (statusResId >= 0) {
statusText = mContext.getString(statusResId);
final Spannable colorStr = new SpannableString(statusText);
if (showInRed) {
colorStr.setSpan(new ForegroundColorSpan(
mContext.getResources().getColor(R.color.timestamp_text_failed)),
0, statusText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
remoteViews.setTextViewText(R.id.date, colorStr);
description.append(separator);
description.append(colorStr);
} else {
description.append(separator);
description.append(Dates.getWidgetTimeString(message.getReceivedTimeStamp(),
false /*abbreviated*/));
}
if (message.hasAttachments()) {
final List<MessagePartData> attachments = message.getAttachments();
int stringId;
for (MessagePartData part : attachments) {
if (part.isImage()) {
stringId = R.string.conversation_list_snippet_picture;
} else if (part.isVideo()) {
stringId = R.string.conversation_list_snippet_video;
} else if (part.isAudio()) {
stringId = R.string.conversation_list_snippet_audio_clip;
} else if (part.isVCard()) {
stringId = R.string.conversation_list_snippet_vcard;
} else {
stringId = 0;
}
if (stringId > 0) {
description.append(separator);
description.append(mContext.getString(stringId));
}
}
}
remoteViews.setContentDescription(message.getIsIncoming() ?
R.id.widget_message_item_incoming :
R.id.widget_message_item_outgoing, description);
}
private Bitmap getAttachmentBitmap(final MessagePartData part) {
UriImageRequestDescriptor descriptor;
if (part.isImage()) {
descriptor = new MessagePartImageRequestDescriptor(part,
IMAGE_ATTACHMENT_SIZE, // desiredWidth
IMAGE_ATTACHMENT_SIZE, // desiredHeight
true // isStatic
);
} else if (part.isVideo()) {
descriptor = new MessagePartVideoThumbnailRequestDescriptor(part);
} else {
return null;
}
final MediaRequest<ImageResource> imageRequest =
descriptor.buildSyncMediaRequest(mContext);
final ImageResource imageResource =
MediaResourceManager.get().requestMediaResourceSync(imageRequest);
if (imageResource != null && imageResource.getBitmap() != null) {
setImageResource(imageResource);
return Bitmap.createBitmap(imageResource.getBitmap());
} else {
releaseImageResource();
return null;
}
}
/**
* @return the "View more messages" view. When the user taps this item, they're
* taken to the conversation in Bugle.
*/
@Override
protected RemoteViews getViewMoreItemsView() {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "getViewMoreConversationsView");
}
final RemoteViews view = new RemoteViews(mContext.getPackageName(),
R.layout.widget_loading);
view.setTextViewText(
R.id.loading_text, mContext.getText(R.string.view_more_messages));
// Tapping this "More messages" item should take us to the conversation.
final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext,
mConversationId, null /* draft */);
view.setOnClickFillInIntent(R.id.widget_loading, intent);
return view;
}
@Override
public RemoteViews getLoadingView() {
final RemoteViews view = new RemoteViews(mContext.getPackageName(),
R.layout.widget_loading);
view.setTextViewText(
R.id.loading_text, mContext.getText(R.string.loading_messages));
return view;
}
@Override
public int getViewTypeCount() {
return 3; // Number of different list items that can be returned -
// 1- incoming list item
// 2- outgoing list item
// 3- more items list item
}
@Override
protected int getMainLayoutId() {
return R.layout.widget_conversation;
}
private void setImageResource(final ImageResource resource) {
if (mImageResource != resource) {
// Clear out any information for what is currently used
releaseImageResource();
mImageResource = resource;
}
}
private void releaseImageResource() {
if (mImageResource != null) {
mImageResource.release();
}
mImageResource = null;
}
}
}