blob: e4817025c347f8717af1e2e2e1f281f7ae687c34 [file] [log] [blame]
Winson Chung499cb9f2010-07-16 11:18:17 -07001/*
2 * Copyright (C) 2007 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 android.widget;
18
Winson Chung3ec9a452010-09-23 16:40:28 -070019import java.lang.ref.WeakReference;
Adam Cohen591ff972012-07-24 22:46:11 -070020import java.util.ArrayList;
Winson Chung499cb9f2010-07-16 11:18:17 -070021import java.util.HashMap;
Winson Chung3ec9a452010-09-23 16:40:28 -070022import java.util.HashSet;
Winson Chungc6d6d4a2010-07-22 13:54:27 -070023import java.util.LinkedList;
Winson Chung81f39eb2011-01-11 18:05:01 -080024import android.appwidget.AppWidgetManager;
Winson Chung499cb9f2010-07-16 11:18:17 -070025import android.content.Context;
26import android.content.Intent;
Winson Chung499cb9f2010-07-16 11:18:17 -070027import android.os.Handler;
28import android.os.HandlerThread;
29import android.os.IBinder;
30import android.os.Looper;
Winson Chung81f39eb2011-01-11 18:05:01 -080031import android.os.Message;
Adam Cohen2625fea2011-03-23 17:24:30 -070032import android.os.RemoteException;
Winson Chungfbc35902010-09-09 16:45:06 -070033import android.util.Log;
Adam Cohen335c3b62012-07-24 17:18:16 -070034import android.util.Pair;
Winson Chunga5f6f802010-09-29 09:24:21 -070035import android.view.LayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070036import android.view.View;
Adam Cohen181d2e32011-01-17 12:40:29 -080037import android.view.View.MeasureSpec;
Winson Chung84bbb022011-02-21 13:57:45 -080038import android.view.ViewGroup;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070039import android.widget.RemoteViews.OnClickHandler;
Winson Chung499cb9f2010-07-16 11:18:17 -070040
Winson Chung81f39eb2011-01-11 18:05:01 -080041import com.android.internal.widget.IRemoteViewsAdapterConnection;
Winson Chung499cb9f2010-07-16 11:18:17 -070042import com.android.internal.widget.IRemoteViewsFactory;
43
44/**
45 * An adapter to a RemoteViewsService which fetches and caches RemoteViews
46 * to be later inflated as child views.
47 */
48/** @hide */
Winson Chung81f39eb2011-01-11 18:05:01 -080049public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
Winson Chungfbc35902010-09-09 16:45:06 -070050 private static final String TAG = "RemoteViewsAdapter";
Winson Chung499cb9f2010-07-16 11:18:17 -070051
Adam Cohen4a9df8d2012-07-27 15:35:13 -070052 // The max number of items in the cache
Winson Chungb90a91c2011-01-26 13:36:34 -080053 private static final int sDefaultCacheSize = 40;
Winson Chung81f39eb2011-01-11 18:05:01 -080054 // The delay (in millis) to wait until attempting to unbind from a service after a request.
55 // This ensures that we don't stay continually bound to the service and that it can be destroyed
56 // if we need the memory elsewhere in the system.
Winson Chungb90a91c2011-01-26 13:36:34 -080057 private static final int sUnbindServiceDelay = 5000;
Adam Cohenb7ffea62011-07-14 14:45:07 -070058
59 // Default height for the default loading view, in case we cannot get inflate the first view
60 private static final int sDefaultLoadingViewHeight = 50;
61
Winson Chung81f39eb2011-01-11 18:05:01 -080062 // Type defs for controlling different messages across the main and worker message queues
Adam Cohen4a9df8d2012-07-27 15:35:13 -070063 private static final int sDefaultMessageType = 0;
Winson Chung81f39eb2011-01-11 18:05:01 -080064 private static final int sUnbindServiceMessageType = 1;
65
66 private final Context mContext;
67 private final Intent mIntent;
68 private final int mAppWidgetId;
Winson Chunga5f6f802010-09-29 09:24:21 -070069 private LayoutInflater mLayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070070 private RemoteViewsAdapterServiceConnection mServiceConnection;
Winson Chung3ec9a452010-09-23 16:40:28 -070071 private WeakReference<RemoteAdapterConnectionCallback> mCallback;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070072 private OnClickHandler mRemoteViewsOnClickHandler;
Winson Chung3ec9a452010-09-23 16:40:28 -070073 private FixedSizeRemoteViewsCache mCache;
Adam Cohenb9673922012-01-05 13:58:47 -080074 private int mVisibleWindowLowerBound;
75 private int mVisibleWindowUpperBound;
Winson Chung3ec9a452010-09-23 16:40:28 -070076
Winson Chung16c8d8a2011-01-20 16:19:33 -080077 // A flag to determine whether we should notify data set changed after we connect
78 private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
79
Winson Chung3ec9a452010-09-23 16:40:28 -070080 // The set of requested views that are to be notified when the associated RemoteViews are
81 // loaded.
82 private RemoteViewsFrameLayoutRefSet mRequestedViews;
Winson Chung499cb9f2010-07-16 11:18:17 -070083
84 private HandlerThread mWorkerThread;
85 // items may be interrupted within the normally processed queues
86 private Handler mWorkerQueue;
87 private Handler mMainQueue;
Winson Chung499cb9f2010-07-16 11:18:17 -070088
Adam Cohen335c3b62012-07-24 17:18:16 -070089 // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
90 // structures;
Adam Cohen4a9df8d2012-07-27 15:35:13 -070091 private static final HashMap<Pair<Intent.FilterComparison, Integer>, FixedSizeRemoteViewsCache>
Adam Cohen335c3b62012-07-24 17:18:16 -070092 sCachedRemoteViewsCaches = new HashMap<Pair<Intent.FilterComparison, Integer>,
Adam Cohen4a9df8d2012-07-27 15:35:13 -070093 FixedSizeRemoteViewsCache>();
Adam Cohen335c3b62012-07-24 17:18:16 -070094 private static final HashMap<Pair<Intent.FilterComparison, Integer>, Runnable>
95 sRemoteViewsCacheRemoveRunnables = new HashMap<Pair<Intent.FilterComparison, Integer>,
96 Runnable>();
97 private static HandlerThread sCacheRemovalThread;
98 private static Handler sCacheRemovalQueue;
99
100 // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation.
101 // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this
102 // duration, the cache is dropped.
103 private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
104
105 // Used to indicate to the AdapterView that it can use this Adapter immediately after
106 // construction (happens when we have a cached FixedSizeRemoteViewsCache).
107 private boolean mDataReady = false;
108
Winson Chung499cb9f2010-07-16 11:18:17 -0700109 /**
110 * An interface for the RemoteAdapter to notify other classes when adapters
111 * are actually connected to/disconnected from their actual services.
112 */
113 public interface RemoteAdapterConnectionCallback {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800114 /**
115 * @return whether the adapter was set or not.
116 */
117 public boolean onRemoteAdapterConnected();
Winson Chung499cb9f2010-07-16 11:18:17 -0700118
119 public void onRemoteAdapterDisconnected();
Adam Cohen2148d432011-07-28 14:59:54 -0700120
121 /**
122 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
123 * connected yet.
124 */
125 public void deferNotifyDataSetChanged();
Winson Chung499cb9f2010-07-16 11:18:17 -0700126 }
127
128 /**
129 * The service connection that gets populated when the RemoteViewsService is
Winson Chung3ec9a452010-09-23 16:40:28 -0700130 * bound. This must be a static inner class to ensure that no references to the outer
131 * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
132 * garbage collected, and would cause us to leak activities due to the caching mechanism for
133 * FrameLayouts in the adapter).
Winson Chung499cb9f2010-07-16 11:18:17 -0700134 */
Winson Chung81f39eb2011-01-11 18:05:01 -0800135 private static class RemoteViewsAdapterServiceConnection extends
136 IRemoteViewsAdapterConnection.Stub {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800137 private boolean mIsConnected;
138 private boolean mIsConnecting;
Winson Chung3ec9a452010-09-23 16:40:28 -0700139 private WeakReference<RemoteViewsAdapter> mAdapter;
Winson Chung499cb9f2010-07-16 11:18:17 -0700140 private IRemoteViewsFactory mRemoteViewsFactory;
Winson Chung499cb9f2010-07-16 11:18:17 -0700141
Winson Chung3ec9a452010-09-23 16:40:28 -0700142 public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
143 mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
Winson Chung499cb9f2010-07-16 11:18:17 -0700144 }
145
Winson Chung16c8d8a2011-01-20 16:19:33 -0800146 public synchronized void bind(Context context, int appWidgetId, Intent intent) {
147 if (!mIsConnecting) {
148 try {
149 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
150 mgr.bindRemoteViewsService(appWidgetId, intent, asBinder());
151 mIsConnecting = true;
152 } catch (Exception e) {
153 Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
154 mIsConnecting = false;
155 mIsConnected = false;
156 }
157 }
158 }
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700159
Winson Chung16c8d8a2011-01-20 16:19:33 -0800160 public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
161 try {
162 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
163 mgr.unbindRemoteViewsService(appWidgetId, intent);
164 mIsConnecting = false;
165 } catch (Exception e) {
166 Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
167 mIsConnecting = false;
168 mIsConnected = false;
169 }
170 }
171
172 public synchronized void onServiceConnected(IBinder service) {
173 mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
174
175 // Remove any deferred unbind messages
Winson Chung3ec9a452010-09-23 16:40:28 -0700176 final RemoteViewsAdapter adapter = mAdapter.get();
177 if (adapter == null) return;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800178
179 // Queue up work that we need to do for the callback to run
Winson Chung3ec9a452010-09-23 16:40:28 -0700180 adapter.mWorkerQueue.post(new Runnable() {
Winson Chung499cb9f2010-07-16 11:18:17 -0700181 @Override
182 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800183 if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
184 // Handle queued notifyDataSetChanged() if necessary
185 adapter.onNotifyDataSetChanged();
186 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700187 IRemoteViewsFactory factory =
188 adapter.mServiceConnection.getRemoteViewsFactory();
189 try {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800190 if (!factory.isCreated()) {
191 // We only call onDataSetChanged() if this is the factory was just
192 // create in response to this bind
193 factory.onDataSetChanged();
194 }
Adam Cohen2625fea2011-03-23 17:24:30 -0700195 } catch (RemoteException e) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700196 Log.e(TAG, "Error notifying factory of data set changed in " +
197 "onServiceConnected(): " + e.getMessage());
Winson Chung499cb9f2010-07-16 11:18:17 -0700198
Winson Chung3ec9a452010-09-23 16:40:28 -0700199 // Return early to prevent anything further from being notified
200 // (effectively nothing has changed)
201 return;
Adam Cohen2625fea2011-03-23 17:24:30 -0700202 } catch (RuntimeException e) {
203 Log.e(TAG, "Error notifying factory of data set changed in " +
204 "onServiceConnected(): " + e.getMessage());
Winson Chung499cb9f2010-07-16 11:18:17 -0700205 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700206
207 // Request meta data so that we have up to date data when calling back to
208 // the remote adapter callback
Winson Chung16c8d8a2011-01-20 16:19:33 -0800209 adapter.updateTemporaryMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700210
Winson Chung16c8d8a2011-01-20 16:19:33 -0800211 // Notify the host that we've connected
Winson Chung61ac7e32010-09-28 10:08:31 -0700212 adapter.mMainQueue.post(new Runnable() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700213 @Override
214 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800215 synchronized (adapter.mCache) {
216 adapter.mCache.commitTemporaryMetaData();
217 }
218
Winson Chung3ec9a452010-09-23 16:40:28 -0700219 final RemoteAdapterConnectionCallback callback =
220 adapter.mCallback.get();
221 if (callback != null) {
222 callback.onRemoteAdapterConnected();
223 }
224 }
225 });
226 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800227
228 // Enqueue unbind message
229 adapter.enqueueDeferredUnbindServiceMessage();
230 mIsConnected = true;
231 mIsConnecting = false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700232 }
233 });
234 }
235
Winson Chung16c8d8a2011-01-20 16:19:33 -0800236 public synchronized void onServiceDisconnected() {
237 mIsConnected = false;
238 mIsConnecting = false;
Winson Chung3ec9a452010-09-23 16:40:28 -0700239 mRemoteViewsFactory = null;
Winson Chung499cb9f2010-07-16 11:18:17 -0700240
Winson Chung16c8d8a2011-01-20 16:19:33 -0800241 // Clear the main/worker queues
Winson Chung3ec9a452010-09-23 16:40:28 -0700242 final RemoteViewsAdapter adapter = mAdapter.get();
243 if (adapter == null) return;
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700244
Winson Chung16c8d8a2011-01-20 16:19:33 -0800245 adapter.mMainQueue.post(new Runnable() {
246 @Override
247 public void run() {
248 // Dequeue any unbind messages
249 adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700250
Winson Chung16c8d8a2011-01-20 16:19:33 -0800251 final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
252 if (callback != null) {
253 callback.onRemoteAdapterDisconnected();
254 }
255 }
256 });
Winson Chung499cb9f2010-07-16 11:18:17 -0700257 }
258
Winson Chung16c8d8a2011-01-20 16:19:33 -0800259 public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
Winson Chung499cb9f2010-07-16 11:18:17 -0700260 return mRemoteViewsFactory;
261 }
262
Winson Chung16c8d8a2011-01-20 16:19:33 -0800263 public synchronized boolean isConnected() {
264 return mIsConnected;
Winson Chung499cb9f2010-07-16 11:18:17 -0700265 }
266 }
267
268 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700269 * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
270 * they are loaded.
Winson Chung499cb9f2010-07-16 11:18:17 -0700271 */
Adam Cohena5a06872012-07-11 15:23:10 -0700272 private static class RemoteViewsFrameLayout extends FrameLayout {
Winson Chung3ec9a452010-09-23 16:40:28 -0700273 public RemoteViewsFrameLayout(Context context) {
274 super(context);
Winson Chung499cb9f2010-07-16 11:18:17 -0700275 }
276
277 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700278 * Updates this RemoteViewsFrameLayout depending on the view that was loaded.
279 * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
280 * successfully.
Winson Chung499cb9f2010-07-16 11:18:17 -0700281 */
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700282 public void onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700283 try {
284 // Remove all the children of this layout first
285 removeAllViews();
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700286 addView(view.apply(getContext(), this, handler));
Winson Chung61ac7e32010-09-28 10:08:31 -0700287 } catch (Exception e) {
288 Log.e(TAG, "Failed to apply RemoteViews.");
289 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700290 }
291 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700292
Winson Chung3ec9a452010-09-23 16:40:28 -0700293 /**
294 * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
295 * adapter that have not yet had their RemoteViews loaded.
296 */
297 private class RemoteViewsFrameLayoutRefSet {
298 private HashMap<Integer, LinkedList<RemoteViewsFrameLayout>> mReferences;
299
300 public RemoteViewsFrameLayoutRefSet() {
301 mReferences = new HashMap<Integer, LinkedList<RemoteViewsFrameLayout>>();
302 }
303
304 /**
305 * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
306 */
307 public void add(int position, RemoteViewsFrameLayout layout) {
308 final Integer pos = position;
309 LinkedList<RemoteViewsFrameLayout> refs;
310
311 // Create the list if necessary
312 if (mReferences.containsKey(pos)) {
313 refs = mReferences.get(pos);
314 } else {
315 refs = new LinkedList<RemoteViewsFrameLayout>();
316 mReferences.put(pos, refs);
Winson Chung499cb9f2010-07-16 11:18:17 -0700317 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700318
319 // Add the references to the list
320 refs.add(layout);
Winson Chung499cb9f2010-07-16 11:18:17 -0700321 }
322
Winson Chung3ec9a452010-09-23 16:40:28 -0700323 /**
324 * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
325 * the associated RemoteViews has loaded.
326 */
Adam Cohena5a06872012-07-11 15:23:10 -0700327 public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700328 if (view == null) return;
329
Winson Chung3ec9a452010-09-23 16:40:28 -0700330 final Integer pos = position;
331 if (mReferences.containsKey(pos)) {
332 // Notify all the references for that position of the newly loaded RemoteViews
333 final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos);
334 for (final RemoteViewsFrameLayout ref : refs) {
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700335 ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler);
Winson Chung499cb9f2010-07-16 11:18:17 -0700336 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700337 refs.clear();
338
339 // Remove this set from the original mapping
340 mReferences.remove(pos);
Winson Chung499cb9f2010-07-16 11:18:17 -0700341 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700342 }
343
Winson Chung3ec9a452010-09-23 16:40:28 -0700344 /**
345 * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
346 */
347 public void clear() {
348 // We currently just clear the references, and leave all the previous layouts returned
349 // in their default state of the loading view.
350 mReferences.clear();
351 }
352 }
353
354 /**
355 * The meta-data associated with the cache in it's current state.
356 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700357 private static class RemoteViewsMetaData {
Winson Chung3ec9a452010-09-23 16:40:28 -0700358 int count;
359 int viewTypeCount;
360 boolean hasStableIds;
Winson Chung3ec9a452010-09-23 16:40:28 -0700361
362 // Used to determine how to construct loading views. If a loading view is not specified
363 // by the user, then we try and load the first view, and use its height as the height for
364 // the default loading view.
365 RemoteViews mUserLoadingView;
366 RemoteViews mFirstView;
367 int mFirstViewHeight;
368
369 // A mapping from type id to a set of unique type ids
Winson Chung16c8d8a2011-01-20 16:19:33 -0800370 private final HashMap<Integer, Integer> mTypeIdIndexMap = new HashMap<Integer, Integer>();
Winson Chung3ec9a452010-09-23 16:40:28 -0700371
372 public RemoteViewsMetaData() {
373 reset();
Winson Chung499cb9f2010-07-16 11:18:17 -0700374 }
375
Winson Chung16c8d8a2011-01-20 16:19:33 -0800376 public void set(RemoteViewsMetaData d) {
377 synchronized (d) {
378 count = d.count;
379 viewTypeCount = d.viewTypeCount;
380 hasStableIds = d.hasStableIds;
381 setLoadingViewTemplates(d.mUserLoadingView, d.mFirstView);
382 }
383 }
384
Winson Chung3ec9a452010-09-23 16:40:28 -0700385 public void reset() {
386 count = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800387
Winson Chung3ec9a452010-09-23 16:40:28 -0700388 // by default there is at least one dummy view type
389 viewTypeCount = 1;
390 hasStableIds = true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700391 mUserLoadingView = null;
392 mFirstView = null;
393 mFirstViewHeight = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800394 mTypeIdIndexMap.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700395 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700396
Winson Chung3ec9a452010-09-23 16:40:28 -0700397 public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
398 mUserLoadingView = loadingView;
399 if (firstView != null) {
400 mFirstView = firstView;
401 mFirstViewHeight = -1;
402 }
403 }
Winson Chungfbc35902010-09-09 16:45:06 -0700404
Winson Chung3ec9a452010-09-23 16:40:28 -0700405 public int getMappedViewType(int typeId) {
406 if (mTypeIdIndexMap.containsKey(typeId)) {
407 return mTypeIdIndexMap.get(typeId);
408 } else {
409 // We +1 because the loading view always has view type id of 0
410 int incrementalTypeId = mTypeIdIndexMap.size() + 1;
411 mTypeIdIndexMap.put(typeId, incrementalTypeId);
412 return incrementalTypeId;
413 }
414 }
415
Adam Cohena5a06872012-07-11 15:23:10 -0700416 public boolean isViewTypeInRange(int typeId) {
417 int mappedType = getMappedViewType(typeId);
418 if (mappedType >= viewTypeCount) {
419 return false;
420 } else {
421 return true;
422 }
423 }
424
Winson Chung3ec9a452010-09-23 16:40:28 -0700425 private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700426 ViewGroup parent, Object lock, LayoutInflater layoutInflater, OnClickHandler
427 handler) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700428 // Create and return a new FrameLayout, and setup the references for this position
429 final Context context = parent.getContext();
430 RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
431
432 // Create a new loading view
Adam Cohena5a06872012-07-11 15:23:10 -0700433 synchronized (lock) {
Adam Cohenb7ffea62011-07-14 14:45:07 -0700434 boolean customLoadingViewAvailable = false;
435
Winson Chung3ec9a452010-09-23 16:40:28 -0700436 if (mUserLoadingView != null) {
Adam Cohenb7ffea62011-07-14 14:45:07 -0700437 // Try to inflate user-specified loading view
438 try {
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700439 View loadingView = mUserLoadingView.apply(parent.getContext(), parent,
440 handler);
Adam Cohenb7ffea62011-07-14 14:45:07 -0700441 loadingView.setTagInternal(com.android.internal.R.id.rowTypeId,
442 new Integer(0));
443 layout.addView(loadingView);
444 customLoadingViewAvailable = true;
445 } catch (Exception e) {
446 Log.w(TAG, "Error inflating custom loading view, using default loading" +
447 "view instead", e);
448 }
449 }
450 if (!customLoadingViewAvailable) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700451 // A default loading view
452 // Use the size of the first row as a guide for the size of the loading view
453 if (mFirstViewHeight < 0) {
Adam Cohenb7ffea62011-07-14 14:45:07 -0700454 try {
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700455 View firstView = mFirstView.apply(parent.getContext(), parent, handler);
Adam Cohenb7ffea62011-07-14 14:45:07 -0700456 firstView.measure(
457 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
458 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
459 mFirstViewHeight = firstView.getMeasuredHeight();
460 mFirstView = null;
461 } catch (Exception e) {
Adam Cohena5a06872012-07-11 15:23:10 -0700462 float density = context.getResources().getDisplayMetrics().density;
Adam Cohenb7ffea62011-07-14 14:45:07 -0700463 mFirstViewHeight = (int)
464 Math.round(sDefaultLoadingViewHeight * density);
465 mFirstView = null;
466 Log.w(TAG, "Error inflating first RemoteViews" + e);
467 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700468 }
469
Winson Chung3ec9a452010-09-23 16:40:28 -0700470 // Compose the loading view text
Adam Cohena5a06872012-07-11 15:23:10 -0700471 TextView loadingTextView = (TextView) layoutInflater.inflate(
Adam Cohenfb603862010-12-17 12:03:17 -0800472 com.android.internal.R.layout.remote_views_adapter_default_loading_view,
473 layout, false);
Winson Chunga5f6f802010-09-29 09:24:21 -0700474 loadingTextView.setHeight(mFirstViewHeight);
475 loadingTextView.setTag(new Integer(0));
Winson Chung3ec9a452010-09-23 16:40:28 -0700476
Winson Chunga5f6f802010-09-29 09:24:21 -0700477 layout.addView(loadingTextView);
Winson Chung499cb9f2010-07-16 11:18:17 -0700478 }
479 }
480
Winson Chung3ec9a452010-09-23 16:40:28 -0700481 return layout;
482 }
483 }
484
485 /**
486 * The meta-data associated with a single item in the cache.
487 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700488 private static class RemoteViewsIndexMetaData {
Winson Chung3ec9a452010-09-23 16:40:28 -0700489 int typeId;
490 long itemId;
491
Adam Cohen591ff972012-07-24 22:46:11 -0700492 public RemoteViewsIndexMetaData(RemoteViews v, long itemId) {
493 set(v, itemId);
Winson Chung3ec9a452010-09-23 16:40:28 -0700494 }
495
Adam Cohen591ff972012-07-24 22:46:11 -0700496 public void set(RemoteViews v, long id) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700497 itemId = id;
Adam Cohena5a06872012-07-11 15:23:10 -0700498 if (v != null) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700499 typeId = v.getLayoutId();
Adam Cohena5a06872012-07-11 15:23:10 -0700500 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700501 typeId = 0;
Adam Cohena5a06872012-07-11 15:23:10 -0700502 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700503 }
504 }
505
506 /**
507 *
508 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700509 private static class FixedSizeRemoteViewsCache {
Winson Chung3ec9a452010-09-23 16:40:28 -0700510 private static final String TAG = "FixedSizeRemoteViewsCache";
511
512 // The meta data related to all the RemoteViews, ie. count, is stable, etc.
Adam Cohen4c994982012-04-02 13:30:32 -0700513 // The meta data objects are made final so that they can be locked on independently
514 // of the FixedSizeRemoteViewsCache. If we ever lock on both meta data objects, it is in
515 // the order mTemporaryMetaData followed by mMetaData.
516 private final RemoteViewsMetaData mMetaData;
517 private final RemoteViewsMetaData mTemporaryMetaData;
Winson Chung3ec9a452010-09-23 16:40:28 -0700518
519 // The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
520 // greater than or equal to the set of RemoteViews.
521 // Note: The reason that we keep this separate from the RemoteViews cache below is that this
522 // we still need to be able to access the mapping of position to meta data, without keeping
523 // the heavy RemoteViews around. The RemoteViews cache is trimmed to fixed constraints wrt.
524 // memory and size, but this metadata cache will retain information until the data at the
525 // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
526 private HashMap<Integer, RemoteViewsIndexMetaData> mIndexMetaData;
527
528 // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
529 // too much memory.
530 private HashMap<Integer, RemoteViews> mIndexRemoteViews;
531
532 // The set of indices that have been explicitly requested by the collection view
533 private HashSet<Integer> mRequestedIndices;
534
Winson Chungb90a91c2011-01-26 13:36:34 -0800535 // We keep a reference of the last requested index to determine which item to prune the
536 // farthest items from when we hit the memory limit
537 private int mLastRequestedIndex;
538
Winson Chung3ec9a452010-09-23 16:40:28 -0700539 // The set of indices to load, including those explicitly requested, as well as those
540 // determined by the preloading algorithm to be prefetched
541 private HashSet<Integer> mLoadIndices;
542
543 // The lower and upper bounds of the preloaded range
544 private int mPreloadLowerBound;
545 private int mPreloadUpperBound;
546
547 // The bounds of this fixed cache, we will try and fill as many items into the cache up to
548 // the maxCount number of items, or the maxSize memory usage.
549 // The maxCountSlack is used to determine if a new position in the cache to be loaded is
550 // sufficiently ouside the old set, prompting a shifting of the "window" of items to be
551 // preloaded.
552 private int mMaxCount;
553 private int mMaxCountSlack;
554 private static final float sMaxCountSlackPercent = 0.75f;
Winson Chungb90a91c2011-01-26 13:36:34 -0800555 private static final int sMaxMemoryLimitInBytes = 2 * 1024 * 1024;
Winson Chung3ec9a452010-09-23 16:40:28 -0700556
557 public FixedSizeRemoteViewsCache(int maxCacheSize) {
558 mMaxCount = maxCacheSize;
559 mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
560 mPreloadLowerBound = 0;
561 mPreloadUpperBound = -1;
562 mMetaData = new RemoteViewsMetaData();
Winson Chung16c8d8a2011-01-20 16:19:33 -0800563 mTemporaryMetaData = new RemoteViewsMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700564 mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
565 mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
566 mRequestedIndices = new HashSet<Integer>();
Winson Chungb90a91c2011-01-26 13:36:34 -0800567 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700568 mLoadIndices = new HashSet<Integer>();
569 }
570
Adam Cohen591ff972012-07-24 22:46:11 -0700571 public void insert(int position, RemoteViews v, long itemId,
572 ArrayList<Integer> visibleWindow) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700573 // Trim the cache if we go beyond the count
574 if (mIndexRemoteViews.size() >= mMaxCount) {
Adam Cohen591ff972012-07-24 22:46:11 -0700575 mIndexRemoteViews.remove(getFarthestPositionFrom(position, visibleWindow));
Winson Chung499cb9f2010-07-16 11:18:17 -0700576 }
577
Winson Chung3ec9a452010-09-23 16:40:28 -0700578 // Trim the cache if we go beyond the available memory size constraints
Winson Chungb90a91c2011-01-26 13:36:34 -0800579 int pruneFromPosition = (mLastRequestedIndex > -1) ? mLastRequestedIndex : position;
580 while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryLimitInBytes) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700581 // Note: This is currently the most naive mechanism for deciding what to prune when
582 // we hit the memory limit. In the future, we may want to calculate which index to
583 // remove based on both its position as well as it's current memory usage, as well
584 // as whether it was directly requested vs. whether it was preloaded by our caching
585 // mechanism.
Adam Cohen591ff972012-07-24 22:46:11 -0700586 mIndexRemoteViews.remove(getFarthestPositionFrom(pruneFromPosition, visibleWindow));
Winson Chung3ec9a452010-09-23 16:40:28 -0700587 }
588
589 // Update the metadata cache
590 if (mIndexMetaData.containsKey(position)) {
591 final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
Adam Cohen591ff972012-07-24 22:46:11 -0700592 metaData.set(v, itemId);
Winson Chung3ec9a452010-09-23 16:40:28 -0700593 } else {
Adam Cohen591ff972012-07-24 22:46:11 -0700594 mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId));
Winson Chung3ec9a452010-09-23 16:40:28 -0700595 }
596 mIndexRemoteViews.put(position, v);
597 }
598
599 public RemoteViewsMetaData getMetaData() {
600 return mMetaData;
601 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800602 public RemoteViewsMetaData getTemporaryMetaData() {
603 return mTemporaryMetaData;
604 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700605 public RemoteViews getRemoteViewsAt(int position) {
606 if (mIndexRemoteViews.containsKey(position)) {
607 return mIndexRemoteViews.get(position);
608 }
609 return null;
610 }
611 public RemoteViewsIndexMetaData getMetaDataAt(int position) {
612 if (mIndexMetaData.containsKey(position)) {
613 return mIndexMetaData.get(position);
614 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700615 return null;
616 }
617
Winson Chung16c8d8a2011-01-20 16:19:33 -0800618 public void commitTemporaryMetaData() {
619 synchronized (mTemporaryMetaData) {
620 synchronized (mMetaData) {
621 mMetaData.set(mTemporaryMetaData);
622 }
623 }
624 }
625
Winson Chung3ec9a452010-09-23 16:40:28 -0700626 private int getRemoteViewsBitmapMemoryUsage() {
627 // Calculate the memory usage of all the RemoteViews bitmaps being cached
628 int mem = 0;
629 for (Integer i : mIndexRemoteViews.keySet()) {
630 final RemoteViews v = mIndexRemoteViews.get(i);
Winson Chungaaffa8b2010-10-30 14:04:05 -0700631 if (v != null) {
Adam Cohen5d200642012-04-24 10:43:31 -0700632 mem += v.estimateMemoryUsage();
Winson Chungaaffa8b2010-10-30 14:04:05 -0700633 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700634 }
635 return mem;
636 }
Adam Cohen591ff972012-07-24 22:46:11 -0700637
638 private int getFarthestPositionFrom(int pos, ArrayList<Integer> visibleWindow) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700639 // Find the index farthest away and remove that
640 int maxDist = 0;
641 int maxDistIndex = -1;
Adam Cohen591ff972012-07-24 22:46:11 -0700642 int maxDistNotVisible = 0;
643 int maxDistIndexNotVisible = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700644 for (int i : mIndexRemoteViews.keySet()) {
645 int dist = Math.abs(i-pos);
Adam Cohen591ff972012-07-24 22:46:11 -0700646 if (dist > maxDistNotVisible && !visibleWindow.contains(i)) {
647 // maxDistNotVisible/maxDistIndexNotVisible will store the index of the
648 // farthest non-visible position
649 maxDistIndexNotVisible = i;
650 maxDistNotVisible = dist;
Winson Chungb90a91c2011-01-26 13:36:34 -0800651 }
Adam Cohen35fbe2a2012-05-22 14:10:14 -0700652 if (dist >= maxDist) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800653 // maxDist/maxDistIndex will store the index of the farthest position
Adam Cohen591ff972012-07-24 22:46:11 -0700654 // regardless of whether it is visible or not
Winson Chung3ec9a452010-09-23 16:40:28 -0700655 maxDistIndex = i;
656 maxDist = dist;
Winson Chung499cb9f2010-07-16 11:18:17 -0700657 }
658 }
Adam Cohen591ff972012-07-24 22:46:11 -0700659 if (maxDistIndexNotVisible > -1) {
660 return maxDistIndexNotVisible;
Winson Chungb90a91c2011-01-26 13:36:34 -0800661 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700662 return maxDistIndex;
Winson Chung499cb9f2010-07-16 11:18:17 -0700663 }
664
Winson Chung3ec9a452010-09-23 16:40:28 -0700665 public void queueRequestedPositionToLoad(int position) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800666 mLastRequestedIndex = position;
Winson Chung3ec9a452010-09-23 16:40:28 -0700667 synchronized (mLoadIndices) {
668 mRequestedIndices.add(position);
669 mLoadIndices.add(position);
670 }
671 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800672 public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700673 // Check if we need to preload any items
674 if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
675 int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
676 if (Math.abs(position - center) < mMaxCountSlack) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800677 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700678 }
679 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700680
Winson Chung3ec9a452010-09-23 16:40:28 -0700681 int count = 0;
682 synchronized (mMetaData) {
683 count = mMetaData.count;
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700684 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700685 synchronized (mLoadIndices) {
686 mLoadIndices.clear();
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700687
Winson Chung3ec9a452010-09-23 16:40:28 -0700688 // Add all the requested indices
689 mLoadIndices.addAll(mRequestedIndices);
Winson Chung499cb9f2010-07-16 11:18:17 -0700690
Winson Chung3ec9a452010-09-23 16:40:28 -0700691 // Add all the preload indices
692 int halfMaxCount = mMaxCount / 2;
693 mPreloadLowerBound = position - halfMaxCount;
694 mPreloadUpperBound = position + halfMaxCount;
695 int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
696 int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
697 for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
698 mLoadIndices.add(i);
Winson Chung499cb9f2010-07-16 11:18:17 -0700699 }
700
Winson Chung3ec9a452010-09-23 16:40:28 -0700701 // But remove all the indices that have already been loaded and are cached
702 mLoadIndices.removeAll(mIndexRemoteViews.keySet());
703 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800704 return true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700705 }
Winson Chungb90a91c2011-01-26 13:36:34 -0800706 /** Returns the next index to load, and whether that index was directly requested or not */
707 public int[] getNextIndexToLoad() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700708 // We try and prioritize items that have been requested directly, instead
709 // of items that are loaded as a result of the caching mechanism
710 synchronized (mLoadIndices) {
711 // Prioritize requested indices to be loaded first
712 if (!mRequestedIndices.isEmpty()) {
713 Integer i = mRequestedIndices.iterator().next();
714 mRequestedIndices.remove(i);
715 mLoadIndices.remove(i);
Winson Chungb90a91c2011-01-26 13:36:34 -0800716 return new int[]{i.intValue(), 1};
Winson Chung3ec9a452010-09-23 16:40:28 -0700717 }
718
719 // Otherwise, preload other indices as necessary
720 if (!mLoadIndices.isEmpty()) {
721 Integer i = mLoadIndices.iterator().next();
722 mLoadIndices.remove(i);
Winson Chungb90a91c2011-01-26 13:36:34 -0800723 return new int[]{i.intValue(), 0};
Winson Chung3ec9a452010-09-23 16:40:28 -0700724 }
725
Winson Chungb90a91c2011-01-26 13:36:34 -0800726 return new int[]{-1, 0};
Winson Chung3ec9a452010-09-23 16:40:28 -0700727 }
728 }
729
730 public boolean containsRemoteViewAt(int position) {
731 return mIndexRemoteViews.containsKey(position);
732 }
733 public boolean containsMetaDataAt(int position) {
734 return mIndexMetaData.containsKey(position);
735 }
736
737 public void reset() {
Winson Chung61ac7e32010-09-28 10:08:31 -0700738 // Note: We do not try and reset the meta data, since that information is still used by
739 // collection views to validate it's own contents (and will be re-requested if the data
740 // is invalidated through the notifyDataSetChanged() flow).
741
Winson Chung3ec9a452010-09-23 16:40:28 -0700742 mPreloadLowerBound = 0;
743 mPreloadUpperBound = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800744 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700745 mIndexRemoteViews.clear();
746 mIndexMetaData.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700747 synchronized (mLoadIndices) {
748 mRequestedIndices.clear();
749 mLoadIndices.clear();
Winson Chung499cb9f2010-07-16 11:18:17 -0700750 }
751 }
752 }
753
754 public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
755 mContext = context;
756 mIntent = intent;
Winson Chung81f39eb2011-01-11 18:05:01 -0800757 mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
Winson Chunga5f6f802010-09-29 09:24:21 -0700758 mLayoutInflater = LayoutInflater.from(context);
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700759 if (mIntent == null) {
760 throw new IllegalArgumentException("Non-null Intent must be specified.");
761 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700762 mRequestedViews = new RemoteViewsFrameLayoutRefSet();
Winson Chung499cb9f2010-07-16 11:18:17 -0700763
Winson Chung81f39eb2011-01-11 18:05:01 -0800764 // Strip the previously injected app widget id from service intent
765 if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
766 intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
767 }
768
769 // Initialize the worker thread
Winson Chung499cb9f2010-07-16 11:18:17 -0700770 mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
771 mWorkerThread.start();
772 mWorkerQueue = new Handler(mWorkerThread.getLooper());
Winson Chung81f39eb2011-01-11 18:05:01 -0800773 mMainQueue = new Handler(Looper.myLooper(), this);
Winson Chung499cb9f2010-07-16 11:18:17 -0700774
Adam Cohen335c3b62012-07-24 17:18:16 -0700775 if (sCacheRemovalThread == null) {
776 sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
777 sCacheRemovalThread.start();
778 sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
779 }
780
Winson Chung81f39eb2011-01-11 18:05:01 -0800781 // Initialize the cache and the service connection on startup
Winson Chung3ec9a452010-09-23 16:40:28 -0700782 mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
783 mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
Adam Cohen335c3b62012-07-24 17:18:16 -0700784
785 Pair<Intent.FilterComparison, Integer> key = new Pair<Intent.FilterComparison, Integer>
786 (new Intent.FilterComparison(mIntent), mAppWidgetId);
787
788 synchronized(sCachedRemoteViewsCaches) {
789 if (sCachedRemoteViewsCaches.containsKey(key)) {
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700790 mCache = sCachedRemoteViewsCaches.get(key);
791 synchronized (mCache.mMetaData) {
792 if (mCache.mMetaData.count > 0) {
793 // As a precautionary measure, we verify that the meta data indicates a
794 // non-zero count before declaring that data is ready.
795 mDataReady = true;
796 }
797 }
Adam Cohen335c3b62012-07-24 17:18:16 -0700798 } else {
799 mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700800 }
801 if (!mDataReady) {
Adam Cohen335c3b62012-07-24 17:18:16 -0700802 requestBindService();
803 }
804 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700805 }
806
Jeff Brownfc442bd2011-06-10 21:34:48 -0700807 @Override
808 protected void finalize() throws Throwable {
809 try {
810 if (mWorkerThread != null) {
811 mWorkerThread.quit();
812 }
813 } finally {
814 super.finalize();
815 }
816 }
817
Adam Cohen335c3b62012-07-24 17:18:16 -0700818 public boolean isDataReady() {
819 return mDataReady;
820 }
821
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700822 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
823 mRemoteViewsOnClickHandler = handler;
824 }
825
Adam Cohen335c3b62012-07-24 17:18:16 -0700826 public void saveRemoteViewsCache() {
827 final Pair<Intent.FilterComparison, Integer> key = new Pair<Intent.FilterComparison,
828 Integer> (new Intent.FilterComparison(mIntent), mAppWidgetId);
829
830 synchronized(sCachedRemoteViewsCaches) {
831 // If we already have a remove runnable posted for this key, remove it.
832 if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
833 sCacheRemovalQueue.removeCallbacks(sRemoteViewsCacheRemoveRunnables.get(key));
834 sRemoteViewsCacheRemoveRunnables.remove(key);
835 }
836
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700837 int metaDataCount = 0;
838 int numRemoteViewsCached = 0;
839 synchronized (mCache.mMetaData) {
840 metaDataCount = mCache.mMetaData.count;
Adam Cohen335c3b62012-07-24 17:18:16 -0700841 }
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700842 synchronized (mCache) {
843 numRemoteViewsCached = mCache.mIndexRemoteViews.size();
844 }
845 if (metaDataCount > 0 && numRemoteViewsCached > 0) {
846 sCachedRemoteViewsCaches.put(key, mCache);
847 }
848
Adam Cohen335c3b62012-07-24 17:18:16 -0700849 Runnable r = new Runnable() {
850 @Override
851 public void run() {
852 synchronized (sCachedRemoteViewsCaches) {
853 if (sCachedRemoteViewsCaches.containsKey(key)) {
854 sCachedRemoteViewsCaches.remove(key);
855 }
856 if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
857 sRemoteViewsCacheRemoveRunnables.remove(key);
858 }
859 }
860 }
861 };
862 sRemoteViewsCacheRemoveRunnables.put(key, r);
863 sCacheRemovalQueue.postDelayed(r, REMOTE_VIEWS_CACHE_DURATION);
864 }
865 }
866
Winson Chung3ec9a452010-09-23 16:40:28 -0700867 private void loadNextIndexInBackground() {
868 mWorkerQueue.post(new Runnable() {
869 @Override
870 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800871 if (mServiceConnection.isConnected()) {
872 // Get the next index to load
873 int position = -1;
874 synchronized (mCache) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800875 int[] res = mCache.getNextIndexToLoad();
876 position = res[0];
Winson Chung16c8d8a2011-01-20 16:19:33 -0800877 }
878 if (position > -1) {
879 // Load the item, and notify any existing RemoteViewsFrameLayouts
Adam Cohen591ff972012-07-24 22:46:11 -0700880 updateRemoteViews(position, true);
Winson Chung3ec9a452010-09-23 16:40:28 -0700881
Winson Chung16c8d8a2011-01-20 16:19:33 -0800882 // Queue up for the next one to load
883 loadNextIndexInBackground();
884 } else {
885 // No more items to load, so queue unbind
886 enqueueDeferredUnbindServiceMessage();
887 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700888 }
889 }
890 });
891 }
892
Winson Chung16c8d8a2011-01-20 16:19:33 -0800893 private void processException(String method, Exception e) {
894 Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -0700895
Winson Chung16c8d8a2011-01-20 16:19:33 -0800896 // If we encounter a crash when updating, we should reset the metadata & cache and trigger
897 // a notifyDataSetChanged to update the widget accordingly
898 final RemoteViewsMetaData metaData = mCache.getMetaData();
899 synchronized (metaData) {
900 metaData.reset();
901 }
902 synchronized (mCache) {
903 mCache.reset();
904 }
905 mMainQueue.post(new Runnable() {
906 @Override
907 public void run() {
908 superNotifyDataSetChanged();
Winson Chung3ec9a452010-09-23 16:40:28 -0700909 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800910 });
911 }
912
913 private void updateTemporaryMetaData() {
914 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
915
916 try {
917 // get the properties/first view (so that we can use it to
918 // measure our dummy views)
919 boolean hasStableIds = factory.hasStableIds();
920 int viewTypeCount = factory.getViewTypeCount();
921 int count = factory.getCount();
922 RemoteViews loadingView = factory.getLoadingView();
923 RemoteViews firstView = null;
924 if ((count > 0) && (loadingView == null)) {
925 firstView = factory.getViewAt(0);
926 }
927 final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
928 synchronized (tmpMetaData) {
929 tmpMetaData.hasStableIds = hasStableIds;
930 // We +1 because the base view type is the loading view
931 tmpMetaData.viewTypeCount = viewTypeCount + 1;
932 tmpMetaData.count = count;
933 tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
934 }
Adam Cohen2625fea2011-03-23 17:24:30 -0700935 } catch(RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800936 processException("updateMetaData", e);
Adam Cohenfa2e3ff2011-04-07 16:48:19 -0700937 } catch(RuntimeException e) {
938 processException("updateMetaData", e);
Winson Chung3ec9a452010-09-23 16:40:28 -0700939 }
940 }
941
Adam Cohen591ff972012-07-24 22:46:11 -0700942 private void updateRemoteViews(final int position, boolean notifyWhenLoaded) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800943 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
Winson Chung3ec9a452010-09-23 16:40:28 -0700944
Winson Chung16c8d8a2011-01-20 16:19:33 -0800945 // Load the item information from the remote service
946 RemoteViews remoteViews = null;
947 long itemId = 0;
948 try {
949 remoteViews = factory.getViewAt(position);
950 itemId = factory.getItemId(position);
Adam Cohen2625fea2011-03-23 17:24:30 -0700951 } catch (RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800952 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -0700953
Winson Chung16c8d8a2011-01-20 16:19:33 -0800954 // Return early to prevent additional work in re-centering the view cache, and
955 // swapping from the loading view
956 return;
Adam Cohen2625fea2011-03-23 17:24:30 -0700957 } catch (RuntimeException e) {
958 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
959 return;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800960 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700961
Winson Chung16c8d8a2011-01-20 16:19:33 -0800962 if (remoteViews == null) {
963 // If a null view was returned, we break early to prevent it from getting
964 // into our cache and causing problems later. The effect is that the child at this
965 // position will remain as a loading view until it is updated.
966 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
967 "returned from RemoteViewsFactory.");
968 return;
969 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700970
Adam Cohena5a06872012-07-11 15:23:10 -0700971 int layoutId = remoteViews.getLayoutId();
972 RemoteViewsMetaData metaData = mCache.getMetaData();
973 boolean viewTypeInRange;
Adam Cohen591ff972012-07-24 22:46:11 -0700974 int cacheCount;
Adam Cohena5a06872012-07-11 15:23:10 -0700975 synchronized (metaData) {
976 viewTypeInRange = metaData.isViewTypeInRange(layoutId);
Adam Cohen591ff972012-07-24 22:46:11 -0700977 cacheCount = mCache.mMetaData.count;
Adam Cohena5a06872012-07-11 15:23:10 -0700978 }
979 synchronized (mCache) {
980 if (viewTypeInRange) {
Adam Cohen591ff972012-07-24 22:46:11 -0700981 ArrayList<Integer> visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
982 mVisibleWindowUpperBound, cacheCount);
Adam Cohena5a06872012-07-11 15:23:10 -0700983 // Cache the RemoteViews we loaded
Adam Cohen591ff972012-07-24 22:46:11 -0700984 mCache.insert(position, remoteViews, itemId, visibleWindow);
Adam Cohena5a06872012-07-11 15:23:10 -0700985
986 // Notify all the views that we have previously returned for this index that
987 // there is new data for it.
988 final RemoteViews rv = remoteViews;
989 if (notifyWhenLoaded) {
990 mMainQueue.post(new Runnable() {
991 @Override
992 public void run() {
993 mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
994 }
995 });
996 }
997 } else {
998 // We need to log an error here, as the the view type count specified by the
999 // factory is less than the number of view types returned. We don't return this
1000 // view to the AdapterView, as this will cause an exception in the hosting process,
1001 // which contains the associated AdapterView.
1002 Log.e(TAG, "Error: widget's RemoteViewsFactory returns more view types than " +
1003 " indicated by getViewTypeCount() ");
Adam Cohenb9673922012-01-05 13:58:47 -08001004 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001005 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001006 }
1007
Winson Chung9b3a2cf2010-09-16 14:45:32 -07001008 public Intent getRemoteViewsServiceIntent() {
1009 return mIntent;
1010 }
1011
Winson Chung499cb9f2010-07-16 11:18:17 -07001012 public int getCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001013 final RemoteViewsMetaData metaData = mCache.getMetaData();
1014 synchronized (metaData) {
1015 return metaData.count;
1016 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001017 }
1018
1019 public Object getItem(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001020 // Disallow arbitrary object to be associated with an item for the time being
Winson Chung499cb9f2010-07-16 11:18:17 -07001021 return null;
1022 }
1023
1024 public long getItemId(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001025 synchronized (mCache) {
1026 if (mCache.containsMetaDataAt(position)) {
1027 return mCache.getMetaDataAt(position).itemId;
1028 }
1029 return 0;
1030 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001031 }
1032
1033 public int getItemViewType(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001034 int typeId = 0;
1035 synchronized (mCache) {
1036 if (mCache.containsMetaDataAt(position)) {
1037 typeId = mCache.getMetaDataAt(position).typeId;
1038 } else {
1039 return 0;
1040 }
1041 }
1042
1043 final RemoteViewsMetaData metaData = mCache.getMetaData();
1044 synchronized (metaData) {
1045 return metaData.getMappedViewType(typeId);
1046 }
1047 }
1048
1049 /**
1050 * Returns the item type id for the specified convert view. Returns -1 if the convert view
1051 * is invalid.
1052 */
1053 private int getConvertViewTypeId(View convertView) {
1054 int typeId = -1;
Adam Cohena32edd42010-10-26 10:35:01 -07001055 if (convertView != null) {
1056 Object tag = convertView.getTag(com.android.internal.R.id.rowTypeId);
1057 if (tag != null) {
1058 typeId = (Integer) tag;
1059 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001060 }
1061 return typeId;
Winson Chung499cb9f2010-07-16 11:18:17 -07001062 }
1063
Adam Cohenb9673922012-01-05 13:58:47 -08001064 /**
1065 * This method allows an AdapterView using this Adapter to provide information about which
1066 * views are currently being displayed. This allows for certain optimizations and preloading
1067 * which wouldn't otherwise be possible.
1068 */
1069 public void setVisibleRangeHint(int lowerBound, int upperBound) {
1070 mVisibleWindowLowerBound = lowerBound;
1071 mVisibleWindowUpperBound = upperBound;
1072 }
1073
Winson Chung499cb9f2010-07-16 11:18:17 -07001074 public View getView(int position, View convertView, ViewGroup parent) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001075 // "Request" an index so that we can queue it for loading, initiate subsequent
1076 // preloading, etc.
1077 synchronized (mCache) {
1078 boolean isInCache = mCache.containsRemoteViewAt(position);
1079 boolean isConnected = mServiceConnection.isConnected();
1080 boolean hasNewItems = false;
1081
Winson Chung7ab73e72011-03-04 15:47:23 -08001082 if (!isInCache && !isConnected) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001083 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
1084 // in turn trigger another request to getView()
1085 requestBindService();
1086 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -07001087 // Queue up other indices to be preloaded based on this position
Winson Chung16c8d8a2011-01-20 16:19:33 -08001088 hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
1089 }
1090
1091 if (isInCache) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001092 View convertViewChild = null;
1093 int convertViewTypeId = 0;
Adam Cohen181d2e32011-01-17 12:40:29 -08001094 RemoteViewsFrameLayout layout = null;
1095
1096 if (convertView instanceof RemoteViewsFrameLayout) {
1097 layout = (RemoteViewsFrameLayout) convertView;
Winson Chung3ec9a452010-09-23 16:40:28 -07001098 convertViewChild = layout.getChildAt(0);
1099 convertViewTypeId = getConvertViewTypeId(convertViewChild);
1100 }
1101
1102 // Second, we try and retrieve the RemoteViews from the cache, returning a loading
1103 // view and queueing it to be loaded if it has not already been loaded.
Winson Chung16c8d8a2011-01-20 16:19:33 -08001104 Context context = parent.getContext();
1105 RemoteViews rv = mCache.getRemoteViewsAt(position);
Adam Cohenaeb66ca2011-02-10 16:08:12 -08001106 RemoteViewsIndexMetaData indexMetaData = mCache.getMetaDataAt(position);
Adam Cohenaeb66ca2011-02-10 16:08:12 -08001107 int typeId = indexMetaData.typeId;
Winson Chung3ec9a452010-09-23 16:40:28 -07001108
Adam Cohenb7ffea62011-07-14 14:45:07 -07001109 try {
1110 // Reuse the convert view where possible
1111 if (layout != null) {
1112 if (convertViewTypeId == typeId) {
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001113 rv.reapply(context, convertViewChild, mRemoteViewsOnClickHandler);
Adam Cohenb7ffea62011-07-14 14:45:07 -07001114 return layout;
1115 }
1116 layout.removeAllViews();
1117 } else {
1118 layout = new RemoteViewsFrameLayout(context);
Winson Chung3ec9a452010-09-23 16:40:28 -07001119 }
Adam Cohenb7ffea62011-07-14 14:45:07 -07001120
1121 // Otherwise, create a new view to be returned
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001122 View newView = rv.apply(context, parent, mRemoteViewsOnClickHandler);
Adam Cohenb7ffea62011-07-14 14:45:07 -07001123 newView.setTagInternal(com.android.internal.R.id.rowTypeId,
1124 new Integer(typeId));
1125 layout.addView(newView);
1126 return layout;
1127
1128 } catch (Exception e){
1129 // We have to make sure that we successfully inflated the RemoteViews, if not
1130 // we return the loading view instead.
1131 Log.w(TAG, "Error inflating RemoteViews at position: " + position + ", using" +
1132 "loading view instead" + e);
1133
1134 RemoteViewsFrameLayout loadingView = null;
1135 final RemoteViewsMetaData metaData = mCache.getMetaData();
1136 synchronized (metaData) {
Adam Cohena5a06872012-07-11 15:23:10 -07001137 loadingView = metaData.createLoadingView(position, convertView, parent,
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001138 mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
Adam Cohenb7ffea62011-07-14 14:45:07 -07001139 }
1140 return loadingView;
1141 } finally {
1142 if (hasNewItems) loadNextIndexInBackground();
Winson Chung3ec9a452010-09-23 16:40:28 -07001143 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08001144 } else {
1145 // If the cache does not have the RemoteViews at this position, then create a
1146 // loading view and queue the actual position to be loaded in the background
1147 RemoteViewsFrameLayout loadingView = null;
1148 final RemoteViewsMetaData metaData = mCache.getMetaData();
1149 synchronized (metaData) {
Adam Cohena5a06872012-07-11 15:23:10 -07001150 loadingView = metaData.createLoadingView(position, convertView, parent,
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001151 mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
Winson Chung16c8d8a2011-01-20 16:19:33 -08001152 }
1153
1154 mRequestedViews.add(position, loadingView);
1155 mCache.queueRequestedPositionToLoad(position);
1156 loadNextIndexInBackground();
1157
1158 return loadingView;
Winson Chung3ec9a452010-09-23 16:40:28 -07001159 }
1160 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001161 }
1162
1163 public int getViewTypeCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001164 final RemoteViewsMetaData metaData = mCache.getMetaData();
1165 synchronized (metaData) {
1166 return metaData.viewTypeCount;
1167 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001168 }
1169
1170 public boolean hasStableIds() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001171 final RemoteViewsMetaData metaData = mCache.getMetaData();
1172 synchronized (metaData) {
1173 return metaData.hasStableIds;
1174 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001175 }
1176
1177 public boolean isEmpty() {
1178 return getCount() <= 0;
1179 }
1180
Winson Chung16c8d8a2011-01-20 16:19:33 -08001181 private void onNotifyDataSetChanged() {
1182 // Complete the actual notifyDataSetChanged() call initiated earlier
1183 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
1184 try {
1185 factory.onDataSetChanged();
Adam Cohen2625fea2011-03-23 17:24:30 -07001186 } catch (RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001187 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
1188
1189 // Return early to prevent from further being notified (since nothing has
1190 // changed)
1191 return;
Adam Cohen2625fea2011-03-23 17:24:30 -07001192 } catch (RuntimeException e) {
1193 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
1194 return;
Winson Chung16c8d8a2011-01-20 16:19:33 -08001195 }
1196
1197 // Flush the cache so that we can reload new items from the service
1198 synchronized (mCache) {
1199 mCache.reset();
1200 }
1201
1202 // Re-request the new metadata (only after the notification to the factory)
1203 updateTemporaryMetaData();
Adam Cohen4c994982012-04-02 13:30:32 -07001204 int newCount;
Adam Cohen591ff972012-07-24 22:46:11 -07001205 ArrayList<Integer> visibleWindow;
Adam Cohen4c994982012-04-02 13:30:32 -07001206 synchronized(mCache.getTemporaryMetaData()) {
1207 newCount = mCache.getTemporaryMetaData().count;
Adam Cohen591ff972012-07-24 22:46:11 -07001208 visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
1209 mVisibleWindowUpperBound, newCount);
Adam Cohen4c994982012-04-02 13:30:32 -07001210 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08001211
Adam Cohenb9673922012-01-05 13:58:47 -08001212 // Pre-load (our best guess of) the views which are currently visible in the AdapterView.
1213 // This mitigates flashing and flickering of loading views when a widget notifies that
1214 // its data has changed.
Adam Cohen591ff972012-07-24 22:46:11 -07001215 for (int i: visibleWindow) {
Adam Cohen4c994982012-04-02 13:30:32 -07001216 // Because temporary meta data is only ever modified from this thread (ie.
1217 // mWorkerThread), it is safe to assume that count is a valid representation.
1218 if (i < newCount) {
Adam Cohen591ff972012-07-24 22:46:11 -07001219 updateRemoteViews(i, false);
Adam Cohen4c994982012-04-02 13:30:32 -07001220 }
Adam Cohenb9673922012-01-05 13:58:47 -08001221 }
1222
Winson Chung16c8d8a2011-01-20 16:19:33 -08001223 // Propagate the notification back to the base adapter
1224 mMainQueue.post(new Runnable() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001225 @Override
1226 public void run() {
Winson Chung6364f2b2010-09-29 11:14:30 -07001227 synchronized (mCache) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001228 mCache.commitTemporaryMetaData();
Winson Chung6364f2b2010-09-29 11:14:30 -07001229 }
1230
Winson Chung16c8d8a2011-01-20 16:19:33 -08001231 superNotifyDataSetChanged();
1232 enqueueDeferredUnbindServiceMessage();
Winson Chung3ec9a452010-09-23 16:40:28 -07001233 }
1234 });
Winson Chung6364f2b2010-09-29 11:14:30 -07001235
Winson Chung16c8d8a2011-01-20 16:19:33 -08001236 // Reset the notify flagflag
1237 mNotifyDataSetChangedAfterOnServiceConnected = false;
1238 }
1239
Adam Cohen591ff972012-07-24 22:46:11 -07001240 private ArrayList<Integer> getVisibleWindow(int lower, int upper, int count) {
1241 ArrayList<Integer> window = new ArrayList<Integer>();
Adam Cohen4a9df8d2012-07-27 15:35:13 -07001242
1243 // In the case that the window is invalid or uninitialized, return an empty window.
1244 if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
1245 return window;
1246 }
1247
Adam Cohen591ff972012-07-24 22:46:11 -07001248 if (lower <= upper) {
1249 for (int i = lower; i <= upper; i++){
1250 window.add(i);
1251 }
1252 } else {
1253 // If the upper bound is less than the lower bound it means that the visible window
1254 // wraps around.
1255 for (int i = lower; i < count; i++) {
1256 window.add(i);
1257 }
1258 for (int i = 0; i <= upper; i++) {
1259 window.add(i);
1260 }
1261 }
1262 return window;
1263 }
1264
Winson Chung16c8d8a2011-01-20 16:19:33 -08001265 public void notifyDataSetChanged() {
1266 // Dequeue any unbind messages
1267 mMainQueue.removeMessages(sUnbindServiceMessageType);
1268
1269 // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
1270 // connect
1271 if (!mServiceConnection.isConnected()) {
1272 if (mNotifyDataSetChangedAfterOnServiceConnected) {
1273 return;
1274 }
1275
1276 mNotifyDataSetChangedAfterOnServiceConnected = true;
1277 requestBindService();
1278 return;
1279 }
1280
1281 mWorkerQueue.post(new Runnable() {
1282 @Override
1283 public void run() {
1284 onNotifyDataSetChanged();
1285 }
1286 });
Winson Chung3ec9a452010-09-23 16:40:28 -07001287 }
1288
Adam Cohenfb603862010-12-17 12:03:17 -08001289 void superNotifyDataSetChanged() {
Winson Chung499cb9f2010-07-16 11:18:17 -07001290 super.notifyDataSetChanged();
1291 }
1292
Winson Chung81f39eb2011-01-11 18:05:01 -08001293 @Override
1294 public boolean handleMessage(Message msg) {
1295 boolean result = false;
1296 switch (msg.what) {
1297 case sUnbindServiceMessageType:
Winson Chung81f39eb2011-01-11 18:05:01 -08001298 if (mServiceConnection.isConnected()) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001299 mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
Winson Chung81f39eb2011-01-11 18:05:01 -08001300 }
1301 result = true;
1302 break;
1303 default:
1304 break;
1305 }
1306 return result;
1307 }
1308
1309 private void enqueueDeferredUnbindServiceMessage() {
1310 // Remove any existing deferred-unbind messages
1311 mMainQueue.removeMessages(sUnbindServiceMessageType);
1312 mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
1313 }
1314
Winson Chung499cb9f2010-07-16 11:18:17 -07001315 private boolean requestBindService() {
Winson Chung81f39eb2011-01-11 18:05:01 -08001316 // Try binding the service (which will start it if it's not already running)
Winson Chung499cb9f2010-07-16 11:18:17 -07001317 if (!mServiceConnection.isConnected()) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001318 mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
Winson Chung499cb9f2010-07-16 11:18:17 -07001319 }
1320
Winson Chung16c8d8a2011-01-20 16:19:33 -08001321 // Remove any existing deferred-unbind messages
1322 mMainQueue.removeMessages(sUnbindServiceMessageType);
Winson Chung499cb9f2010-07-16 11:18:17 -07001323 return mServiceConnection.isConnected();
1324 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001325}