Revert "Initial checkin of AOSP Messaging app."
This reverts commit 461a34b466.
Change-Id: Iac4ca77eeaa94989e91dead49a7959c905bd3078
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user