Home | History | Annotate | Download | only in widget
      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