1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.messaging.widget; 18 19 import android.appwidget.AppWidgetManager; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.database.Cursor; 23 import android.graphics.Bitmap; 24 import android.graphics.Typeface; 25 import android.net.Uri; 26 import android.os.Binder; 27 import android.text.Spannable; 28 import android.text.SpannableStringBuilder; 29 import android.text.style.StyleSpan; 30 import android.widget.RemoteViews; 31 import android.widget.RemoteViewsService; 32 33 import com.android.messaging.R; 34 import com.android.messaging.datamodel.media.AvatarGroupRequestDescriptor; 35 import com.android.messaging.datamodel.media.AvatarRequestDescriptor; 36 import com.android.messaging.datamodel.media.ImageRequestDescriptor; 37 import com.android.messaging.datamodel.media.ImageResource; 38 import com.android.messaging.datamodel.media.MediaRequest; 39 import com.android.messaging.datamodel.media.MediaResourceManager; 40 import com.android.messaging.util.AvatarUriUtil; 41 import com.android.messaging.util.LogUtil; 42 43 /** 44 * Remote Views Factory for Bugle Widget. 45 */ 46 abstract class BaseWidgetFactory implements RemoteViewsService.RemoteViewsFactory { 47 protected static final String TAG = LogUtil.BUGLE_WIDGET_TAG; 48 49 protected static final int MAX_ITEMS_TO_SHOW = 25; 50 51 /** 52 * Lock to avoid race condition between widgets. 53 */ 54 protected static final Object sWidgetLock = new Object(); 55 56 protected final Context mContext; 57 protected final int mAppWidgetId; 58 protected boolean mShouldShowViewMore; 59 protected Cursor mCursor; 60 protected final AppWidgetManager mAppWidgetManager; 61 protected int mIconSize; 62 protected ImageResource mAvatarResource; 63 64 public BaseWidgetFactory(Context context, Intent intent) { 65 mContext = context; 66 mAppWidgetId = intent.getIntExtra( 67 AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); 68 mAppWidgetManager = AppWidgetManager.getInstance(context); 69 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { 70 LogUtil.v(TAG, "BaseWidgetFactory intent: " + intent + "widget id: " + mAppWidgetId); 71 } 72 mIconSize = (int) context.getResources() 73 .getDimension(R.dimen.contact_icon_view_normal_size); 74 75 } 76 77 @Override 78 public void onCreate() { 79 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { 80 LogUtil.v(TAG, "onCreate"); 81 } 82 } 83 84 @Override 85 public void onDestroy() { 86 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { 87 LogUtil.v(TAG, "onDestroy"); 88 } 89 synchronized (sWidgetLock) { 90 if (mCursor != null && !mCursor.isClosed()) { 91 mCursor.close(); 92 mCursor = null; 93 } 94 } 95 } 96 97 @Override 98 public void onDataSetChanged() { 99 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { 100 LogUtil.v(TAG, "onDataSetChanged"); 101 } 102 synchronized (sWidgetLock) { 103 if (mCursor != null) { 104 mCursor.close(); 105 mCursor = null; 106 } 107 final long token = Binder.clearCallingIdentity(); 108 try { 109 mCursor = doQuery(); 110 onLoadComplete(); 111 } finally { 112 Binder.restoreCallingIdentity(token); 113 } 114 } 115 } 116 117 protected abstract Cursor doQuery(); 118 119 /** 120 * Returns the number of items that should be shown in the widget list. This method also 121 * updates the boolean that indicates whether the "show more" item should be shown. 122 * @return the number of items to be displayed in the list. 123 */ 124 @Override 125 public int getCount() { 126 synchronized (sWidgetLock) { 127 if (mCursor == null) { 128 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { 129 LogUtil.v(TAG, "getCount: 0"); 130 } 131 return 0; 132 } 133 final int count = getItemCount(); 134 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { 135 LogUtil.v(TAG, "getCount: " + count); 136 } 137 mShouldShowViewMore = count < mCursor.getCount(); 138 return count + (mShouldShowViewMore ? 1 : 0); 139 } 140 } 141 142 /** 143 * Returns the number of messages that should be shown in the widget. This method 144 * doesn't update the boolean that indicates whether the "show more" item should be included 145 * in the list. 146 * @return 147 */ 148 protected int getItemCount() { 149 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { 150 LogUtil.v(TAG, "getItemCount: " + mCursor.getCount()); 151 } 152 return Math.min(mCursor.getCount(), MAX_ITEMS_TO_SHOW); 153 } 154 155 /* 156 * Make the given text bold if the item is unread 157 */ 158 protected CharSequence boldifyIfUnread(CharSequence text, final boolean unread) { 159 if (!unread) { 160 return text; 161 } 162 final SpannableStringBuilder builder = new SpannableStringBuilder(text); 163 builder.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(), 164 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 165 return builder; 166 } 167 168 protected Bitmap getAvatarBitmap(final Uri avatarUri) { 169 final String avatarType = avatarUri == null ? 170 null : AvatarUriUtil.getAvatarType(avatarUri); 171 ImageRequestDescriptor descriptor; 172 if (AvatarUriUtil.TYPE_GROUP_URI.equals(avatarType)) { 173 descriptor = new AvatarGroupRequestDescriptor(avatarUri, mIconSize, mIconSize); 174 } else { 175 descriptor = new AvatarRequestDescriptor(avatarUri, mIconSize, mIconSize); 176 } 177 178 final MediaRequest<ImageResource> imageRequest = 179 descriptor.buildSyncMediaRequest(mContext); 180 final ImageResource imageResource = 181 MediaResourceManager.get().requestMediaResourceSync(imageRequest); 182 if (imageResource != null) { 183 setAvatarResource(imageResource); 184 return mAvatarResource.getBitmap(); 185 } else { 186 releaseAvatarResource(); 187 return null; 188 } 189 } 190 191 /** 192 * @return the "View more messages" view. When the user taps this item, they're 193 * taken to the conversation in Bugle. 194 */ 195 abstract protected RemoteViews getViewMoreItemsView(); 196 197 @Override 198 public boolean hasStableIds() { 199 return true; 200 } 201 202 @Override 203 public long getItemId(int position) { 204 return position; 205 } 206 207 private void onLoadComplete() { 208 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) { 209 LogUtil.v(TAG, "onLoadComplete"); 210 } 211 final RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), 212 getMainLayoutId()); 213 mAppWidgetManager.partiallyUpdateAppWidget(mAppWidgetId, remoteViews); 214 } 215 216 protected abstract int getMainLayoutId(); 217 218 private void setAvatarResource(final ImageResource resource) { 219 if (mAvatarResource != resource) { 220 // Clear out any information for what is currently used 221 releaseAvatarResource(); 222 mAvatarResource = resource; 223 } 224 } 225 226 private void releaseAvatarResource() { 227 if (mAvatarResource != null) { 228 mAvatarResource.release(); 229 } 230 mAvatarResource = null; 231 } 232 } 233