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
17package com.android.messaging.widget;
18
19import android.appwidget.AppWidgetManager;
20import android.content.Context;
21import android.content.Intent;
22import android.database.Cursor;
23import android.graphics.Bitmap;
24import android.graphics.Typeface;
25import android.net.Uri;
26import android.os.Binder;
27import android.text.Spannable;
28import android.text.SpannableStringBuilder;
29import android.text.style.StyleSpan;
30import android.widget.RemoteViews;
31import android.widget.RemoteViewsService;
32
33import com.android.messaging.R;
34import com.android.messaging.datamodel.media.AvatarGroupRequestDescriptor;
35import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
36import com.android.messaging.datamodel.media.ImageRequestDescriptor;
37import com.android.messaging.datamodel.media.ImageResource;
38import com.android.messaging.datamodel.media.MediaRequest;
39import com.android.messaging.datamodel.media.MediaResourceManager;
40import com.android.messaging.util.AvatarUriUtil;
41import com.android.messaging.util.LogUtil;
42
43/**
44 * Remote Views Factory for Bugle Widget.
45 */
46abstract 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