blob: aeee1115429a0c46cafaf86599a6fb062694a809 [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;
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -080024
Winson Chung81f39eb2011-01-11 18:05:01 -080025import android.appwidget.AppWidgetManager;
Winson Chung499cb9f2010-07-16 11:18:17 -070026import android.content.Context;
27import android.content.Intent;
Winson Chung499cb9f2010-07-16 11:18:17 -070028import android.os.Handler;
29import android.os.HandlerThread;
30import android.os.IBinder;
31import android.os.Looper;
Winson Chung81f39eb2011-01-11 18:05:01 -080032import android.os.Message;
Amith Yamasanic566b432012-11-30 15:26:21 -080033import android.os.Process;
Adam Cohen2625fea2011-03-23 17:24:30 -070034import android.os.RemoteException;
Amith Yamasanic566b432012-11-30 15:26:21 -080035import android.os.UserHandle;
Winson Chungfbc35902010-09-09 16:45:06 -070036import android.util.Log;
Winson Chunga5f6f802010-09-29 09:24:21 -070037import android.view.LayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070038import android.view.View;
Adam Cohen181d2e32011-01-17 12:40:29 -080039import android.view.View.MeasureSpec;
Winson Chung84bbb022011-02-21 13:57:45 -080040import android.view.ViewGroup;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070041import android.widget.RemoteViews.OnClickHandler;
Winson Chung499cb9f2010-07-16 11:18:17 -070042
Winson Chung81f39eb2011-01-11 18:05:01 -080043import com.android.internal.widget.IRemoteViewsAdapterConnection;
Winson Chung499cb9f2010-07-16 11:18:17 -070044import com.android.internal.widget.IRemoteViewsFactory;
Amith Yamasanic566b432012-11-30 15:26:21 -080045import com.android.internal.widget.LockPatternUtils;
Winson Chung499cb9f2010-07-16 11:18:17 -070046
47/**
48 * An adapter to a RemoteViewsService which fetches and caches RemoteViews
49 * to be later inflated as child views.
50 */
51/** @hide */
Winson Chung81f39eb2011-01-11 18:05:01 -080052public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
Winson Chungfbc35902010-09-09 16:45:06 -070053 private static final String TAG = "RemoteViewsAdapter";
Winson Chung499cb9f2010-07-16 11:18:17 -070054
Adam Cohen4a9df8d2012-07-27 15:35:13 -070055 // The max number of items in the cache
Winson Chungb90a91c2011-01-26 13:36:34 -080056 private static final int sDefaultCacheSize = 40;
Winson Chung81f39eb2011-01-11 18:05:01 -080057 // The delay (in millis) to wait until attempting to unbind from a service after a request.
58 // This ensures that we don't stay continually bound to the service and that it can be destroyed
59 // if we need the memory elsewhere in the system.
Winson Chungb90a91c2011-01-26 13:36:34 -080060 private static final int sUnbindServiceDelay = 5000;
Adam Cohenb7ffea62011-07-14 14:45:07 -070061
62 // Default height for the default loading view, in case we cannot get inflate the first view
63 private static final int sDefaultLoadingViewHeight = 50;
64
Winson Chung81f39eb2011-01-11 18:05:01 -080065 // Type defs for controlling different messages across the main and worker message queues
Adam Cohen4a9df8d2012-07-27 15:35:13 -070066 private static final int sDefaultMessageType = 0;
Winson Chung81f39eb2011-01-11 18:05:01 -080067 private static final int sUnbindServiceMessageType = 1;
68
69 private final Context mContext;
70 private final Intent mIntent;
71 private final int mAppWidgetId;
Winson Chunga5f6f802010-09-29 09:24:21 -070072 private LayoutInflater mLayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070073 private RemoteViewsAdapterServiceConnection mServiceConnection;
Winson Chung3ec9a452010-09-23 16:40:28 -070074 private WeakReference<RemoteAdapterConnectionCallback> mCallback;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070075 private OnClickHandler mRemoteViewsOnClickHandler;
Winson Chung3ec9a452010-09-23 16:40:28 -070076 private FixedSizeRemoteViewsCache mCache;
Adam Cohenb9673922012-01-05 13:58:47 -080077 private int mVisibleWindowLowerBound;
78 private int mVisibleWindowUpperBound;
Winson Chung3ec9a452010-09-23 16:40:28 -070079
Winson Chung16c8d8a2011-01-20 16:19:33 -080080 // A flag to determine whether we should notify data set changed after we connect
81 private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
82
Winson Chung3ec9a452010-09-23 16:40:28 -070083 // The set of requested views that are to be notified when the associated RemoteViews are
84 // loaded.
85 private RemoteViewsFrameLayoutRefSet mRequestedViews;
Winson Chung499cb9f2010-07-16 11:18:17 -070086
87 private HandlerThread mWorkerThread;
88 // items may be interrupted within the normally processed queues
89 private Handler mWorkerQueue;
90 private Handler mMainQueue;
Winson Chung499cb9f2010-07-16 11:18:17 -070091
Adam Cohen335c3b62012-07-24 17:18:16 -070092 // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -080093 // structures;
94 private static final HashMap<RemoteViewsCacheKey,
95 FixedSizeRemoteViewsCache> sCachedRemoteViewsCaches
96 = new HashMap<RemoteViewsCacheKey,
Adam Cohen4a9df8d2012-07-27 15:35:13 -070097 FixedSizeRemoteViewsCache>();
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -080098 private static final HashMap<RemoteViewsCacheKey, Runnable>
99 sRemoteViewsCacheRemoveRunnables
100 = new HashMap<RemoteViewsCacheKey, Runnable>();
101
Adam Cohen335c3b62012-07-24 17:18:16 -0700102 private static HandlerThread sCacheRemovalThread;
103 private static Handler sCacheRemovalQueue;
104
105 // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation.
106 // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this
107 // duration, the cache is dropped.
108 private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
109
110 // Used to indicate to the AdapterView that it can use this Adapter immediately after
111 // construction (happens when we have a cached FixedSizeRemoteViewsCache).
112 private boolean mDataReady = false;
113
Amith Yamasanic566b432012-11-30 15:26:21 -0800114 int mUserId;
115
Winson Chung499cb9f2010-07-16 11:18:17 -0700116 /**
117 * An interface for the RemoteAdapter to notify other classes when adapters
118 * are actually connected to/disconnected from their actual services.
119 */
120 public interface RemoteAdapterConnectionCallback {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800121 /**
122 * @return whether the adapter was set or not.
123 */
124 public boolean onRemoteAdapterConnected();
Winson Chung499cb9f2010-07-16 11:18:17 -0700125
126 public void onRemoteAdapterDisconnected();
Adam Cohen2148d432011-07-28 14:59:54 -0700127
128 /**
129 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
130 * connected yet.
131 */
132 public void deferNotifyDataSetChanged();
Winson Chung499cb9f2010-07-16 11:18:17 -0700133 }
134
135 /**
136 * The service connection that gets populated when the RemoteViewsService is
Winson Chung3ec9a452010-09-23 16:40:28 -0700137 * bound. This must be a static inner class to ensure that no references to the outer
138 * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
139 * garbage collected, and would cause us to leak activities due to the caching mechanism for
140 * FrameLayouts in the adapter).
Winson Chung499cb9f2010-07-16 11:18:17 -0700141 */
Winson Chung81f39eb2011-01-11 18:05:01 -0800142 private static class RemoteViewsAdapterServiceConnection extends
143 IRemoteViewsAdapterConnection.Stub {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800144 private boolean mIsConnected;
145 private boolean mIsConnecting;
Winson Chung3ec9a452010-09-23 16:40:28 -0700146 private WeakReference<RemoteViewsAdapter> mAdapter;
Winson Chung499cb9f2010-07-16 11:18:17 -0700147 private IRemoteViewsFactory mRemoteViewsFactory;
Winson Chung499cb9f2010-07-16 11:18:17 -0700148
Winson Chung3ec9a452010-09-23 16:40:28 -0700149 public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
150 mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
Winson Chung499cb9f2010-07-16 11:18:17 -0700151 }
152
Winson Chung16c8d8a2011-01-20 16:19:33 -0800153 public synchronized void bind(Context context, int appWidgetId, Intent intent) {
154 if (!mIsConnecting) {
155 try {
Amith Yamasanic566b432012-11-30 15:26:21 -0800156 RemoteViewsAdapter adapter;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800157 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
Amith Yamasanic566b432012-11-30 15:26:21 -0800158 if (Process.myUid() == Process.SYSTEM_UID
159 && (adapter = mAdapter.get()) != null) {
160 mgr.bindRemoteViewsService(appWidgetId, intent, asBinder(),
161 new UserHandle(adapter.mUserId));
162 } else {
163 mgr.bindRemoteViewsService(appWidgetId, intent, asBinder(),
164 Process.myUserHandle());
165 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800166 mIsConnecting = true;
167 } catch (Exception e) {
168 Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
169 mIsConnecting = false;
170 mIsConnected = false;
171 }
172 }
173 }
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700174
Winson Chung16c8d8a2011-01-20 16:19:33 -0800175 public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
176 try {
Amith Yamasanic566b432012-11-30 15:26:21 -0800177 RemoteViewsAdapter adapter;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800178 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
Amith Yamasanic566b432012-11-30 15:26:21 -0800179 if (Process.myUid() == Process.SYSTEM_UID
180 && (adapter = mAdapter.get()) != null) {
181 mgr.unbindRemoteViewsService(appWidgetId, intent,
182 new UserHandle(adapter.mUserId));
183 } else {
184 mgr.unbindRemoteViewsService(appWidgetId, intent, Process.myUserHandle());
185 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800186 mIsConnecting = false;
187 } catch (Exception e) {
188 Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
189 mIsConnecting = false;
190 mIsConnected = false;
191 }
192 }
193
194 public synchronized void onServiceConnected(IBinder service) {
195 mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
196
197 // Remove any deferred unbind messages
Winson Chung3ec9a452010-09-23 16:40:28 -0700198 final RemoteViewsAdapter adapter = mAdapter.get();
199 if (adapter == null) return;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800200
201 // Queue up work that we need to do for the callback to run
Winson Chung3ec9a452010-09-23 16:40:28 -0700202 adapter.mWorkerQueue.post(new Runnable() {
Winson Chung499cb9f2010-07-16 11:18:17 -0700203 @Override
204 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800205 if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
206 // Handle queued notifyDataSetChanged() if necessary
207 adapter.onNotifyDataSetChanged();
208 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700209 IRemoteViewsFactory factory =
210 adapter.mServiceConnection.getRemoteViewsFactory();
211 try {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800212 if (!factory.isCreated()) {
213 // We only call onDataSetChanged() if this is the factory was just
214 // create in response to this bind
215 factory.onDataSetChanged();
216 }
Adam Cohen2625fea2011-03-23 17:24:30 -0700217 } catch (RemoteException e) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700218 Log.e(TAG, "Error notifying factory of data set changed in " +
219 "onServiceConnected(): " + e.getMessage());
Winson Chung499cb9f2010-07-16 11:18:17 -0700220
Winson Chung3ec9a452010-09-23 16:40:28 -0700221 // Return early to prevent anything further from being notified
222 // (effectively nothing has changed)
223 return;
Adam Cohen2625fea2011-03-23 17:24:30 -0700224 } catch (RuntimeException e) {
225 Log.e(TAG, "Error notifying factory of data set changed in " +
226 "onServiceConnected(): " + e.getMessage());
Winson Chung499cb9f2010-07-16 11:18:17 -0700227 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700228
229 // Request meta data so that we have up to date data when calling back to
230 // the remote adapter callback
Winson Chung16c8d8a2011-01-20 16:19:33 -0800231 adapter.updateTemporaryMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700232
Winson Chung16c8d8a2011-01-20 16:19:33 -0800233 // Notify the host that we've connected
Winson Chung61ac7e32010-09-28 10:08:31 -0700234 adapter.mMainQueue.post(new Runnable() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700235 @Override
236 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800237 synchronized (adapter.mCache) {
238 adapter.mCache.commitTemporaryMetaData();
239 }
240
Winson Chung3ec9a452010-09-23 16:40:28 -0700241 final RemoteAdapterConnectionCallback callback =
242 adapter.mCallback.get();
243 if (callback != null) {
244 callback.onRemoteAdapterConnected();
245 }
246 }
247 });
248 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800249
250 // Enqueue unbind message
251 adapter.enqueueDeferredUnbindServiceMessage();
252 mIsConnected = true;
253 mIsConnecting = false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700254 }
255 });
256 }
257
Winson Chung16c8d8a2011-01-20 16:19:33 -0800258 public synchronized void onServiceDisconnected() {
259 mIsConnected = false;
260 mIsConnecting = false;
Winson Chung3ec9a452010-09-23 16:40:28 -0700261 mRemoteViewsFactory = null;
Winson Chung499cb9f2010-07-16 11:18:17 -0700262
Winson Chung16c8d8a2011-01-20 16:19:33 -0800263 // Clear the main/worker queues
Winson Chung3ec9a452010-09-23 16:40:28 -0700264 final RemoteViewsAdapter adapter = mAdapter.get();
265 if (adapter == null) return;
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700266
Winson Chung16c8d8a2011-01-20 16:19:33 -0800267 adapter.mMainQueue.post(new Runnable() {
268 @Override
269 public void run() {
270 // Dequeue any unbind messages
271 adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700272
Winson Chung16c8d8a2011-01-20 16:19:33 -0800273 final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
274 if (callback != null) {
275 callback.onRemoteAdapterDisconnected();
276 }
277 }
278 });
Winson Chung499cb9f2010-07-16 11:18:17 -0700279 }
280
Winson Chung16c8d8a2011-01-20 16:19:33 -0800281 public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
Winson Chung499cb9f2010-07-16 11:18:17 -0700282 return mRemoteViewsFactory;
283 }
284
Winson Chung16c8d8a2011-01-20 16:19:33 -0800285 public synchronized boolean isConnected() {
286 return mIsConnected;
Winson Chung499cb9f2010-07-16 11:18:17 -0700287 }
288 }
289
290 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700291 * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
292 * they are loaded.
Winson Chung499cb9f2010-07-16 11:18:17 -0700293 */
Adam Cohena5a06872012-07-11 15:23:10 -0700294 private static class RemoteViewsFrameLayout extends FrameLayout {
Winson Chung3ec9a452010-09-23 16:40:28 -0700295 public RemoteViewsFrameLayout(Context context) {
296 super(context);
Winson Chung499cb9f2010-07-16 11:18:17 -0700297 }
298
299 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700300 * Updates this RemoteViewsFrameLayout depending on the view that was loaded.
301 * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
302 * successfully.
Winson Chung499cb9f2010-07-16 11:18:17 -0700303 */
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700304 public void onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700305 try {
306 // Remove all the children of this layout first
307 removeAllViews();
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700308 addView(view.apply(getContext(), this, handler));
Winson Chung61ac7e32010-09-28 10:08:31 -0700309 } catch (Exception e) {
310 Log.e(TAG, "Failed to apply RemoteViews.");
311 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700312 }
313 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700314
Winson Chung3ec9a452010-09-23 16:40:28 -0700315 /**
316 * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
317 * adapter that have not yet had their RemoteViews loaded.
318 */
319 private class RemoteViewsFrameLayoutRefSet {
320 private HashMap<Integer, LinkedList<RemoteViewsFrameLayout>> mReferences;
Adam Cohenff067192012-12-05 18:14:39 -0800321 private HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>
322 mViewToLinkedList;
Winson Chung3ec9a452010-09-23 16:40:28 -0700323
324 public RemoteViewsFrameLayoutRefSet() {
325 mReferences = new HashMap<Integer, LinkedList<RemoteViewsFrameLayout>>();
Adam Cohenff067192012-12-05 18:14:39 -0800326 mViewToLinkedList =
327 new HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>();
Winson Chung3ec9a452010-09-23 16:40:28 -0700328 }
329
330 /**
331 * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
332 */
333 public void add(int position, RemoteViewsFrameLayout layout) {
334 final Integer pos = position;
335 LinkedList<RemoteViewsFrameLayout> refs;
336
337 // Create the list if necessary
338 if (mReferences.containsKey(pos)) {
339 refs = mReferences.get(pos);
340 } else {
341 refs = new LinkedList<RemoteViewsFrameLayout>();
342 mReferences.put(pos, refs);
Winson Chung499cb9f2010-07-16 11:18:17 -0700343 }
Adam Cohenff067192012-12-05 18:14:39 -0800344 mViewToLinkedList.put(layout, refs);
Winson Chung3ec9a452010-09-23 16:40:28 -0700345
346 // Add the references to the list
347 refs.add(layout);
Winson Chung499cb9f2010-07-16 11:18:17 -0700348 }
349
Winson Chung3ec9a452010-09-23 16:40:28 -0700350 /**
351 * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
352 * the associated RemoteViews has loaded.
353 */
Adam Cohena5a06872012-07-11 15:23:10 -0700354 public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700355 if (view == null) return;
356
Winson Chung3ec9a452010-09-23 16:40:28 -0700357 final Integer pos = position;
358 if (mReferences.containsKey(pos)) {
359 // Notify all the references for that position of the newly loaded RemoteViews
360 final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos);
361 for (final RemoteViewsFrameLayout ref : refs) {
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700362 ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler);
Adam Cohenff067192012-12-05 18:14:39 -0800363 if (mViewToLinkedList.containsKey(ref)) {
364 mViewToLinkedList.remove(ref);
365 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700366 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700367 refs.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700368 // Remove this set from the original mapping
369 mReferences.remove(pos);
Winson Chung499cb9f2010-07-16 11:18:17 -0700370 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700371 }
372
Winson Chung3ec9a452010-09-23 16:40:28 -0700373 /**
Adam Cohenff067192012-12-05 18:14:39 -0800374 * We need to remove views from this set if they have been recycled by the AdapterView.
375 */
376 public void removeView(RemoteViewsFrameLayout rvfl) {
377 if (mViewToLinkedList.containsKey(rvfl)) {
378 mViewToLinkedList.get(rvfl).remove(rvfl);
379 mViewToLinkedList.remove(rvfl);
380 }
381 }
382
383 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700384 * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
385 */
386 public void clear() {
387 // We currently just clear the references, and leave all the previous layouts returned
388 // in their default state of the loading view.
389 mReferences.clear();
Adam Cohenff067192012-12-05 18:14:39 -0800390 mViewToLinkedList.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700391 }
392 }
393
394 /**
395 * The meta-data associated with the cache in it's current state.
396 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700397 private static class RemoteViewsMetaData {
Winson Chung3ec9a452010-09-23 16:40:28 -0700398 int count;
399 int viewTypeCount;
400 boolean hasStableIds;
Winson Chung3ec9a452010-09-23 16:40:28 -0700401
402 // Used to determine how to construct loading views. If a loading view is not specified
403 // by the user, then we try and load the first view, and use its height as the height for
404 // the default loading view.
405 RemoteViews mUserLoadingView;
406 RemoteViews mFirstView;
407 int mFirstViewHeight;
408
409 // A mapping from type id to a set of unique type ids
Winson Chung16c8d8a2011-01-20 16:19:33 -0800410 private final HashMap<Integer, Integer> mTypeIdIndexMap = new HashMap<Integer, Integer>();
Winson Chung3ec9a452010-09-23 16:40:28 -0700411
412 public RemoteViewsMetaData() {
413 reset();
Winson Chung499cb9f2010-07-16 11:18:17 -0700414 }
415
Winson Chung16c8d8a2011-01-20 16:19:33 -0800416 public void set(RemoteViewsMetaData d) {
417 synchronized (d) {
418 count = d.count;
419 viewTypeCount = d.viewTypeCount;
420 hasStableIds = d.hasStableIds;
421 setLoadingViewTemplates(d.mUserLoadingView, d.mFirstView);
422 }
423 }
424
Winson Chung3ec9a452010-09-23 16:40:28 -0700425 public void reset() {
426 count = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800427
Winson Chung3ec9a452010-09-23 16:40:28 -0700428 // by default there is at least one dummy view type
429 viewTypeCount = 1;
430 hasStableIds = true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700431 mUserLoadingView = null;
432 mFirstView = null;
433 mFirstViewHeight = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800434 mTypeIdIndexMap.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700435 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700436
Winson Chung3ec9a452010-09-23 16:40:28 -0700437 public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
438 mUserLoadingView = loadingView;
439 if (firstView != null) {
440 mFirstView = firstView;
441 mFirstViewHeight = -1;
442 }
443 }
Winson Chungfbc35902010-09-09 16:45:06 -0700444
Winson Chung3ec9a452010-09-23 16:40:28 -0700445 public int getMappedViewType(int typeId) {
446 if (mTypeIdIndexMap.containsKey(typeId)) {
447 return mTypeIdIndexMap.get(typeId);
448 } else {
449 // We +1 because the loading view always has view type id of 0
450 int incrementalTypeId = mTypeIdIndexMap.size() + 1;
451 mTypeIdIndexMap.put(typeId, incrementalTypeId);
452 return incrementalTypeId;
453 }
454 }
455
Adam Cohena5a06872012-07-11 15:23:10 -0700456 public boolean isViewTypeInRange(int typeId) {
457 int mappedType = getMappedViewType(typeId);
458 if (mappedType >= viewTypeCount) {
459 return false;
460 } else {
461 return true;
462 }
463 }
464
Winson Chung3ec9a452010-09-23 16:40:28 -0700465 private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700466 ViewGroup parent, Object lock, LayoutInflater layoutInflater, OnClickHandler
467 handler) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700468 // Create and return a new FrameLayout, and setup the references for this position
469 final Context context = parent.getContext();
470 RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
471
472 // Create a new loading view
Adam Cohena5a06872012-07-11 15:23:10 -0700473 synchronized (lock) {
Adam Cohenb7ffea62011-07-14 14:45:07 -0700474 boolean customLoadingViewAvailable = false;
475
Winson Chung3ec9a452010-09-23 16:40:28 -0700476 if (mUserLoadingView != null) {
Adam Cohenb7ffea62011-07-14 14:45:07 -0700477 // Try to inflate user-specified loading view
478 try {
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700479 View loadingView = mUserLoadingView.apply(parent.getContext(), parent,
480 handler);
Adam Cohenb7ffea62011-07-14 14:45:07 -0700481 loadingView.setTagInternal(com.android.internal.R.id.rowTypeId,
482 new Integer(0));
483 layout.addView(loadingView);
484 customLoadingViewAvailable = true;
485 } catch (Exception e) {
486 Log.w(TAG, "Error inflating custom loading view, using default loading" +
487 "view instead", e);
488 }
489 }
490 if (!customLoadingViewAvailable) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700491 // A default loading view
492 // Use the size of the first row as a guide for the size of the loading view
493 if (mFirstViewHeight < 0) {
Adam Cohenb7ffea62011-07-14 14:45:07 -0700494 try {
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700495 View firstView = mFirstView.apply(parent.getContext(), parent, handler);
Adam Cohenb7ffea62011-07-14 14:45:07 -0700496 firstView.measure(
497 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
498 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
499 mFirstViewHeight = firstView.getMeasuredHeight();
500 mFirstView = null;
501 } catch (Exception e) {
Adam Cohena5a06872012-07-11 15:23:10 -0700502 float density = context.getResources().getDisplayMetrics().density;
Adam Cohenb7ffea62011-07-14 14:45:07 -0700503 mFirstViewHeight = (int)
504 Math.round(sDefaultLoadingViewHeight * density);
505 mFirstView = null;
506 Log.w(TAG, "Error inflating first RemoteViews" + e);
507 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700508 }
509
Winson Chung3ec9a452010-09-23 16:40:28 -0700510 // Compose the loading view text
Adam Cohena5a06872012-07-11 15:23:10 -0700511 TextView loadingTextView = (TextView) layoutInflater.inflate(
Adam Cohenfb603862010-12-17 12:03:17 -0800512 com.android.internal.R.layout.remote_views_adapter_default_loading_view,
513 layout, false);
Winson Chunga5f6f802010-09-29 09:24:21 -0700514 loadingTextView.setHeight(mFirstViewHeight);
515 loadingTextView.setTag(new Integer(0));
Winson Chung3ec9a452010-09-23 16:40:28 -0700516
Winson Chunga5f6f802010-09-29 09:24:21 -0700517 layout.addView(loadingTextView);
Winson Chung499cb9f2010-07-16 11:18:17 -0700518 }
519 }
520
Winson Chung3ec9a452010-09-23 16:40:28 -0700521 return layout;
522 }
523 }
524
525 /**
526 * The meta-data associated with a single item in the cache.
527 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700528 private static class RemoteViewsIndexMetaData {
Winson Chung3ec9a452010-09-23 16:40:28 -0700529 int typeId;
530 long itemId;
531
Adam Cohen591ff972012-07-24 22:46:11 -0700532 public RemoteViewsIndexMetaData(RemoteViews v, long itemId) {
533 set(v, itemId);
Winson Chung3ec9a452010-09-23 16:40:28 -0700534 }
535
Adam Cohen591ff972012-07-24 22:46:11 -0700536 public void set(RemoteViews v, long id) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700537 itemId = id;
Adam Cohena5a06872012-07-11 15:23:10 -0700538 if (v != null) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700539 typeId = v.getLayoutId();
Adam Cohena5a06872012-07-11 15:23:10 -0700540 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700541 typeId = 0;
Adam Cohena5a06872012-07-11 15:23:10 -0700542 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700543 }
544 }
545
546 /**
547 *
548 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700549 private static class FixedSizeRemoteViewsCache {
Winson Chung3ec9a452010-09-23 16:40:28 -0700550 private static final String TAG = "FixedSizeRemoteViewsCache";
551
552 // The meta data related to all the RemoteViews, ie. count, is stable, etc.
Adam Cohen4c994982012-04-02 13:30:32 -0700553 // The meta data objects are made final so that they can be locked on independently
554 // of the FixedSizeRemoteViewsCache. If we ever lock on both meta data objects, it is in
555 // the order mTemporaryMetaData followed by mMetaData.
556 private final RemoteViewsMetaData mMetaData;
557 private final RemoteViewsMetaData mTemporaryMetaData;
Winson Chung3ec9a452010-09-23 16:40:28 -0700558
559 // The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
560 // greater than or equal to the set of RemoteViews.
561 // Note: The reason that we keep this separate from the RemoteViews cache below is that this
562 // we still need to be able to access the mapping of position to meta data, without keeping
563 // the heavy RemoteViews around. The RemoteViews cache is trimmed to fixed constraints wrt.
564 // memory and size, but this metadata cache will retain information until the data at the
565 // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
566 private HashMap<Integer, RemoteViewsIndexMetaData> mIndexMetaData;
567
568 // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
569 // too much memory.
570 private HashMap<Integer, RemoteViews> mIndexRemoteViews;
571
572 // The set of indices that have been explicitly requested by the collection view
573 private HashSet<Integer> mRequestedIndices;
574
Winson Chungb90a91c2011-01-26 13:36:34 -0800575 // We keep a reference of the last requested index to determine which item to prune the
576 // farthest items from when we hit the memory limit
577 private int mLastRequestedIndex;
578
Winson Chung3ec9a452010-09-23 16:40:28 -0700579 // The set of indices to load, including those explicitly requested, as well as those
580 // determined by the preloading algorithm to be prefetched
581 private HashSet<Integer> mLoadIndices;
582
583 // The lower and upper bounds of the preloaded range
584 private int mPreloadLowerBound;
585 private int mPreloadUpperBound;
586
587 // The bounds of this fixed cache, we will try and fill as many items into the cache up to
588 // the maxCount number of items, or the maxSize memory usage.
589 // The maxCountSlack is used to determine if a new position in the cache to be loaded is
590 // sufficiently ouside the old set, prompting a shifting of the "window" of items to be
591 // preloaded.
592 private int mMaxCount;
593 private int mMaxCountSlack;
594 private static final float sMaxCountSlackPercent = 0.75f;
Winson Chungb90a91c2011-01-26 13:36:34 -0800595 private static final int sMaxMemoryLimitInBytes = 2 * 1024 * 1024;
Winson Chung3ec9a452010-09-23 16:40:28 -0700596
597 public FixedSizeRemoteViewsCache(int maxCacheSize) {
598 mMaxCount = maxCacheSize;
599 mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
600 mPreloadLowerBound = 0;
601 mPreloadUpperBound = -1;
602 mMetaData = new RemoteViewsMetaData();
Winson Chung16c8d8a2011-01-20 16:19:33 -0800603 mTemporaryMetaData = new RemoteViewsMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700604 mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
605 mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
606 mRequestedIndices = new HashSet<Integer>();
Winson Chungb90a91c2011-01-26 13:36:34 -0800607 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700608 mLoadIndices = new HashSet<Integer>();
609 }
610
Adam Cohen591ff972012-07-24 22:46:11 -0700611 public void insert(int position, RemoteViews v, long itemId,
612 ArrayList<Integer> visibleWindow) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700613 // Trim the cache if we go beyond the count
614 if (mIndexRemoteViews.size() >= mMaxCount) {
Adam Cohen591ff972012-07-24 22:46:11 -0700615 mIndexRemoteViews.remove(getFarthestPositionFrom(position, visibleWindow));
Winson Chung499cb9f2010-07-16 11:18:17 -0700616 }
617
Winson Chung3ec9a452010-09-23 16:40:28 -0700618 // Trim the cache if we go beyond the available memory size constraints
Winson Chungb90a91c2011-01-26 13:36:34 -0800619 int pruneFromPosition = (mLastRequestedIndex > -1) ? mLastRequestedIndex : position;
620 while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryLimitInBytes) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700621 // Note: This is currently the most naive mechanism for deciding what to prune when
622 // we hit the memory limit. In the future, we may want to calculate which index to
623 // remove based on both its position as well as it's current memory usage, as well
624 // as whether it was directly requested vs. whether it was preloaded by our caching
625 // mechanism.
Adam Cohen591ff972012-07-24 22:46:11 -0700626 mIndexRemoteViews.remove(getFarthestPositionFrom(pruneFromPosition, visibleWindow));
Winson Chung3ec9a452010-09-23 16:40:28 -0700627 }
628
629 // Update the metadata cache
630 if (mIndexMetaData.containsKey(position)) {
631 final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
Adam Cohen591ff972012-07-24 22:46:11 -0700632 metaData.set(v, itemId);
Winson Chung3ec9a452010-09-23 16:40:28 -0700633 } else {
Adam Cohen591ff972012-07-24 22:46:11 -0700634 mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId));
Winson Chung3ec9a452010-09-23 16:40:28 -0700635 }
636 mIndexRemoteViews.put(position, v);
637 }
638
639 public RemoteViewsMetaData getMetaData() {
640 return mMetaData;
641 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800642 public RemoteViewsMetaData getTemporaryMetaData() {
643 return mTemporaryMetaData;
644 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700645 public RemoteViews getRemoteViewsAt(int position) {
646 if (mIndexRemoteViews.containsKey(position)) {
647 return mIndexRemoteViews.get(position);
648 }
649 return null;
650 }
651 public RemoteViewsIndexMetaData getMetaDataAt(int position) {
652 if (mIndexMetaData.containsKey(position)) {
653 return mIndexMetaData.get(position);
654 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700655 return null;
656 }
657
Winson Chung16c8d8a2011-01-20 16:19:33 -0800658 public void commitTemporaryMetaData() {
659 synchronized (mTemporaryMetaData) {
660 synchronized (mMetaData) {
661 mMetaData.set(mTemporaryMetaData);
662 }
663 }
664 }
665
Winson Chung3ec9a452010-09-23 16:40:28 -0700666 private int getRemoteViewsBitmapMemoryUsage() {
667 // Calculate the memory usage of all the RemoteViews bitmaps being cached
668 int mem = 0;
669 for (Integer i : mIndexRemoteViews.keySet()) {
670 final RemoteViews v = mIndexRemoteViews.get(i);
Winson Chungaaffa8b2010-10-30 14:04:05 -0700671 if (v != null) {
Adam Cohen5d200642012-04-24 10:43:31 -0700672 mem += v.estimateMemoryUsage();
Winson Chungaaffa8b2010-10-30 14:04:05 -0700673 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700674 }
675 return mem;
676 }
Adam Cohen591ff972012-07-24 22:46:11 -0700677
678 private int getFarthestPositionFrom(int pos, ArrayList<Integer> visibleWindow) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700679 // Find the index farthest away and remove that
680 int maxDist = 0;
681 int maxDistIndex = -1;
Adam Cohen591ff972012-07-24 22:46:11 -0700682 int maxDistNotVisible = 0;
683 int maxDistIndexNotVisible = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700684 for (int i : mIndexRemoteViews.keySet()) {
685 int dist = Math.abs(i-pos);
Adam Cohen591ff972012-07-24 22:46:11 -0700686 if (dist > maxDistNotVisible && !visibleWindow.contains(i)) {
687 // maxDistNotVisible/maxDistIndexNotVisible will store the index of the
688 // farthest non-visible position
689 maxDistIndexNotVisible = i;
690 maxDistNotVisible = dist;
Winson Chungb90a91c2011-01-26 13:36:34 -0800691 }
Adam Cohen35fbe2a2012-05-22 14:10:14 -0700692 if (dist >= maxDist) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800693 // maxDist/maxDistIndex will store the index of the farthest position
Adam Cohen591ff972012-07-24 22:46:11 -0700694 // regardless of whether it is visible or not
Winson Chung3ec9a452010-09-23 16:40:28 -0700695 maxDistIndex = i;
696 maxDist = dist;
Winson Chung499cb9f2010-07-16 11:18:17 -0700697 }
698 }
Adam Cohen591ff972012-07-24 22:46:11 -0700699 if (maxDistIndexNotVisible > -1) {
700 return maxDistIndexNotVisible;
Winson Chungb90a91c2011-01-26 13:36:34 -0800701 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700702 return maxDistIndex;
Winson Chung499cb9f2010-07-16 11:18:17 -0700703 }
704
Winson Chung3ec9a452010-09-23 16:40:28 -0700705 public void queueRequestedPositionToLoad(int position) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800706 mLastRequestedIndex = position;
Winson Chung3ec9a452010-09-23 16:40:28 -0700707 synchronized (mLoadIndices) {
708 mRequestedIndices.add(position);
709 mLoadIndices.add(position);
710 }
711 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800712 public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700713 // Check if we need to preload any items
714 if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
715 int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
716 if (Math.abs(position - center) < mMaxCountSlack) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800717 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700718 }
719 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700720
Winson Chung3ec9a452010-09-23 16:40:28 -0700721 int count = 0;
722 synchronized (mMetaData) {
723 count = mMetaData.count;
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700724 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700725 synchronized (mLoadIndices) {
726 mLoadIndices.clear();
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700727
Winson Chung3ec9a452010-09-23 16:40:28 -0700728 // Add all the requested indices
729 mLoadIndices.addAll(mRequestedIndices);
Winson Chung499cb9f2010-07-16 11:18:17 -0700730
Winson Chung3ec9a452010-09-23 16:40:28 -0700731 // Add all the preload indices
732 int halfMaxCount = mMaxCount / 2;
733 mPreloadLowerBound = position - halfMaxCount;
734 mPreloadUpperBound = position + halfMaxCount;
735 int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
736 int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
737 for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
738 mLoadIndices.add(i);
Winson Chung499cb9f2010-07-16 11:18:17 -0700739 }
740
Winson Chung3ec9a452010-09-23 16:40:28 -0700741 // But remove all the indices that have already been loaded and are cached
742 mLoadIndices.removeAll(mIndexRemoteViews.keySet());
743 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800744 return true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700745 }
Winson Chungb90a91c2011-01-26 13:36:34 -0800746 /** Returns the next index to load, and whether that index was directly requested or not */
747 public int[] getNextIndexToLoad() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700748 // We try and prioritize items that have been requested directly, instead
749 // of items that are loaded as a result of the caching mechanism
750 synchronized (mLoadIndices) {
751 // Prioritize requested indices to be loaded first
752 if (!mRequestedIndices.isEmpty()) {
753 Integer i = mRequestedIndices.iterator().next();
754 mRequestedIndices.remove(i);
755 mLoadIndices.remove(i);
Winson Chungb90a91c2011-01-26 13:36:34 -0800756 return new int[]{i.intValue(), 1};
Winson Chung3ec9a452010-09-23 16:40:28 -0700757 }
758
759 // Otherwise, preload other indices as necessary
760 if (!mLoadIndices.isEmpty()) {
761 Integer i = mLoadIndices.iterator().next();
762 mLoadIndices.remove(i);
Winson Chungb90a91c2011-01-26 13:36:34 -0800763 return new int[]{i.intValue(), 0};
Winson Chung3ec9a452010-09-23 16:40:28 -0700764 }
765
Winson Chungb90a91c2011-01-26 13:36:34 -0800766 return new int[]{-1, 0};
Winson Chung3ec9a452010-09-23 16:40:28 -0700767 }
768 }
769
770 public boolean containsRemoteViewAt(int position) {
771 return mIndexRemoteViews.containsKey(position);
772 }
773 public boolean containsMetaDataAt(int position) {
774 return mIndexMetaData.containsKey(position);
775 }
776
777 public void reset() {
Winson Chung61ac7e32010-09-28 10:08:31 -0700778 // Note: We do not try and reset the meta data, since that information is still used by
779 // collection views to validate it's own contents (and will be re-requested if the data
780 // is invalidated through the notifyDataSetChanged() flow).
781
Winson Chung3ec9a452010-09-23 16:40:28 -0700782 mPreloadLowerBound = 0;
783 mPreloadUpperBound = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800784 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700785 mIndexRemoteViews.clear();
786 mIndexMetaData.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700787 synchronized (mLoadIndices) {
788 mRequestedIndices.clear();
789 mLoadIndices.clear();
Winson Chung499cb9f2010-07-16 11:18:17 -0700790 }
791 }
792 }
793
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800794 static class RemoteViewsCacheKey {
795 final Intent.FilterComparison filter;
796 final int widgetId;
797 final int userId;
798
799 RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId, int userId) {
800 this.filter = filter;
801 this.widgetId = widgetId;
802 this.userId = userId;
803 }
804
805 @Override
806 public boolean equals(Object o) {
807 if (!(o instanceof RemoteViewsCacheKey)) {
808 return false;
809 }
810 RemoteViewsCacheKey other = (RemoteViewsCacheKey) o;
811 return other.filter.equals(filter) && other.widgetId == widgetId
812 && other.userId == userId;
813 }
814
815 @Override
816 public int hashCode() {
817 return (filter == null ? 0 : filter.hashCode()) ^ (widgetId << 2) ^ (userId << 10);
818 }
819 }
820
Winson Chung499cb9f2010-07-16 11:18:17 -0700821 public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
822 mContext = context;
823 mIntent = intent;
Winson Chung81f39eb2011-01-11 18:05:01 -0800824 mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
Winson Chunga5f6f802010-09-29 09:24:21 -0700825 mLayoutInflater = LayoutInflater.from(context);
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700826 if (mIntent == null) {
827 throw new IllegalArgumentException("Non-null Intent must be specified.");
828 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700829 mRequestedViews = new RemoteViewsFrameLayoutRefSet();
Winson Chung499cb9f2010-07-16 11:18:17 -0700830
Amith Yamasanic566b432012-11-30 15:26:21 -0800831 if (Process.myUid() == Process.SYSTEM_UID) {
832 mUserId = new LockPatternUtils(context).getCurrentUser();
833 } else {
834 mUserId = UserHandle.myUserId();
835 }
Winson Chung81f39eb2011-01-11 18:05:01 -0800836 // Strip the previously injected app widget id from service intent
837 if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
838 intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
839 }
840
841 // Initialize the worker thread
Winson Chung499cb9f2010-07-16 11:18:17 -0700842 mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
843 mWorkerThread.start();
844 mWorkerQueue = new Handler(mWorkerThread.getLooper());
Winson Chung81f39eb2011-01-11 18:05:01 -0800845 mMainQueue = new Handler(Looper.myLooper(), this);
Winson Chung499cb9f2010-07-16 11:18:17 -0700846
Adam Cohen335c3b62012-07-24 17:18:16 -0700847 if (sCacheRemovalThread == null) {
848 sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
849 sCacheRemovalThread.start();
850 sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
851 }
852
Winson Chung81f39eb2011-01-11 18:05:01 -0800853 // Initialize the cache and the service connection on startup
Winson Chung3ec9a452010-09-23 16:40:28 -0700854 mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
855 mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
Adam Cohen335c3b62012-07-24 17:18:16 -0700856
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800857 RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
858 mAppWidgetId, mUserId);
Adam Cohen335c3b62012-07-24 17:18:16 -0700859
860 synchronized(sCachedRemoteViewsCaches) {
861 if (sCachedRemoteViewsCaches.containsKey(key)) {
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700862 mCache = sCachedRemoteViewsCaches.get(key);
863 synchronized (mCache.mMetaData) {
864 if (mCache.mMetaData.count > 0) {
865 // As a precautionary measure, we verify that the meta data indicates a
866 // non-zero count before declaring that data is ready.
867 mDataReady = true;
868 }
869 }
Adam Cohen335c3b62012-07-24 17:18:16 -0700870 } else {
871 mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700872 }
873 if (!mDataReady) {
Adam Cohen335c3b62012-07-24 17:18:16 -0700874 requestBindService();
875 }
876 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700877 }
878
Jeff Brownfc442bd2011-06-10 21:34:48 -0700879 @Override
880 protected void finalize() throws Throwable {
881 try {
882 if (mWorkerThread != null) {
883 mWorkerThread.quit();
884 }
885 } finally {
886 super.finalize();
887 }
888 }
889
Adam Cohen335c3b62012-07-24 17:18:16 -0700890 public boolean isDataReady() {
891 return mDataReady;
892 }
893
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700894 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
895 mRemoteViewsOnClickHandler = handler;
896 }
897
Adam Cohen335c3b62012-07-24 17:18:16 -0700898 public void saveRemoteViewsCache() {
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800899 final RemoteViewsCacheKey key = new RemoteViewsCacheKey(
900 new Intent.FilterComparison(mIntent), mAppWidgetId, mUserId);
Adam Cohen335c3b62012-07-24 17:18:16 -0700901
902 synchronized(sCachedRemoteViewsCaches) {
903 // If we already have a remove runnable posted for this key, remove it.
904 if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
905 sCacheRemovalQueue.removeCallbacks(sRemoteViewsCacheRemoveRunnables.get(key));
906 sRemoteViewsCacheRemoveRunnables.remove(key);
907 }
908
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700909 int metaDataCount = 0;
910 int numRemoteViewsCached = 0;
911 synchronized (mCache.mMetaData) {
912 metaDataCount = mCache.mMetaData.count;
Adam Cohen335c3b62012-07-24 17:18:16 -0700913 }
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700914 synchronized (mCache) {
915 numRemoteViewsCached = mCache.mIndexRemoteViews.size();
916 }
917 if (metaDataCount > 0 && numRemoteViewsCached > 0) {
918 sCachedRemoteViewsCaches.put(key, mCache);
919 }
920
Adam Cohen335c3b62012-07-24 17:18:16 -0700921 Runnable r = new Runnable() {
922 @Override
923 public void run() {
924 synchronized (sCachedRemoteViewsCaches) {
925 if (sCachedRemoteViewsCaches.containsKey(key)) {
926 sCachedRemoteViewsCaches.remove(key);
927 }
928 if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
929 sRemoteViewsCacheRemoveRunnables.remove(key);
930 }
931 }
932 }
933 };
934 sRemoteViewsCacheRemoveRunnables.put(key, r);
935 sCacheRemovalQueue.postDelayed(r, REMOTE_VIEWS_CACHE_DURATION);
936 }
937 }
938
Winson Chung3ec9a452010-09-23 16:40:28 -0700939 private void loadNextIndexInBackground() {
940 mWorkerQueue.post(new Runnable() {
941 @Override
942 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800943 if (mServiceConnection.isConnected()) {
944 // Get the next index to load
945 int position = -1;
946 synchronized (mCache) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800947 int[] res = mCache.getNextIndexToLoad();
948 position = res[0];
Winson Chung16c8d8a2011-01-20 16:19:33 -0800949 }
950 if (position > -1) {
951 // Load the item, and notify any existing RemoteViewsFrameLayouts
Adam Cohen591ff972012-07-24 22:46:11 -0700952 updateRemoteViews(position, true);
Winson Chung3ec9a452010-09-23 16:40:28 -0700953
Winson Chung16c8d8a2011-01-20 16:19:33 -0800954 // Queue up for the next one to load
955 loadNextIndexInBackground();
956 } else {
957 // No more items to load, so queue unbind
958 enqueueDeferredUnbindServiceMessage();
959 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700960 }
961 }
962 });
963 }
964
Winson Chung16c8d8a2011-01-20 16:19:33 -0800965 private void processException(String method, Exception e) {
966 Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -0700967
Winson Chung16c8d8a2011-01-20 16:19:33 -0800968 // If we encounter a crash when updating, we should reset the metadata & cache and trigger
969 // a notifyDataSetChanged to update the widget accordingly
970 final RemoteViewsMetaData metaData = mCache.getMetaData();
971 synchronized (metaData) {
972 metaData.reset();
973 }
974 synchronized (mCache) {
975 mCache.reset();
976 }
977 mMainQueue.post(new Runnable() {
978 @Override
979 public void run() {
980 superNotifyDataSetChanged();
Winson Chung3ec9a452010-09-23 16:40:28 -0700981 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800982 });
983 }
984
985 private void updateTemporaryMetaData() {
986 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
987
988 try {
989 // get the properties/first view (so that we can use it to
990 // measure our dummy views)
991 boolean hasStableIds = factory.hasStableIds();
992 int viewTypeCount = factory.getViewTypeCount();
993 int count = factory.getCount();
994 RemoteViews loadingView = factory.getLoadingView();
995 RemoteViews firstView = null;
996 if ((count > 0) && (loadingView == null)) {
997 firstView = factory.getViewAt(0);
998 }
999 final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
1000 synchronized (tmpMetaData) {
1001 tmpMetaData.hasStableIds = hasStableIds;
1002 // We +1 because the base view type is the loading view
1003 tmpMetaData.viewTypeCount = viewTypeCount + 1;
1004 tmpMetaData.count = count;
1005 tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
1006 }
Adam Cohen2625fea2011-03-23 17:24:30 -07001007 } catch(RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001008 processException("updateMetaData", e);
Adam Cohenfa2e3ff2011-04-07 16:48:19 -07001009 } catch(RuntimeException e) {
1010 processException("updateMetaData", e);
Winson Chung3ec9a452010-09-23 16:40:28 -07001011 }
1012 }
1013
Adam Cohen591ff972012-07-24 22:46:11 -07001014 private void updateRemoteViews(final int position, boolean notifyWhenLoaded) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001015 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
Winson Chung3ec9a452010-09-23 16:40:28 -07001016
Winson Chung16c8d8a2011-01-20 16:19:33 -08001017 // Load the item information from the remote service
1018 RemoteViews remoteViews = null;
1019 long itemId = 0;
1020 try {
1021 remoteViews = factory.getViewAt(position);
Amith Yamasani94022e82012-12-04 11:05:39 -08001022 remoteViews.setUser(new UserHandle(mUserId));
Winson Chung16c8d8a2011-01-20 16:19:33 -08001023 itemId = factory.getItemId(position);
Adam Cohen2625fea2011-03-23 17:24:30 -07001024 } catch (RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001025 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -07001026
Winson Chung16c8d8a2011-01-20 16:19:33 -08001027 // Return early to prevent additional work in re-centering the view cache, and
1028 // swapping from the loading view
1029 return;
Adam Cohen2625fea2011-03-23 17:24:30 -07001030 } catch (RuntimeException e) {
1031 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
1032 return;
Winson Chung16c8d8a2011-01-20 16:19:33 -08001033 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001034
Winson Chung16c8d8a2011-01-20 16:19:33 -08001035 if (remoteViews == null) {
1036 // If a null view was returned, we break early to prevent it from getting
1037 // into our cache and causing problems later. The effect is that the child at this
1038 // position will remain as a loading view until it is updated.
1039 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
1040 "returned from RemoteViewsFactory.");
1041 return;
1042 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001043
Adam Cohena5a06872012-07-11 15:23:10 -07001044 int layoutId = remoteViews.getLayoutId();
1045 RemoteViewsMetaData metaData = mCache.getMetaData();
1046 boolean viewTypeInRange;
Adam Cohen591ff972012-07-24 22:46:11 -07001047 int cacheCount;
Adam Cohena5a06872012-07-11 15:23:10 -07001048 synchronized (metaData) {
1049 viewTypeInRange = metaData.isViewTypeInRange(layoutId);
Adam Cohen591ff972012-07-24 22:46:11 -07001050 cacheCount = mCache.mMetaData.count;
Adam Cohena5a06872012-07-11 15:23:10 -07001051 }
1052 synchronized (mCache) {
1053 if (viewTypeInRange) {
Adam Cohen591ff972012-07-24 22:46:11 -07001054 ArrayList<Integer> visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
1055 mVisibleWindowUpperBound, cacheCount);
Adam Cohena5a06872012-07-11 15:23:10 -07001056 // Cache the RemoteViews we loaded
Adam Cohen591ff972012-07-24 22:46:11 -07001057 mCache.insert(position, remoteViews, itemId, visibleWindow);
Adam Cohena5a06872012-07-11 15:23:10 -07001058
1059 // Notify all the views that we have previously returned for this index that
1060 // there is new data for it.
1061 final RemoteViews rv = remoteViews;
1062 if (notifyWhenLoaded) {
1063 mMainQueue.post(new Runnable() {
1064 @Override
1065 public void run() {
1066 mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
1067 }
1068 });
1069 }
1070 } else {
1071 // We need to log an error here, as the the view type count specified by the
1072 // factory is less than the number of view types returned. We don't return this
1073 // view to the AdapterView, as this will cause an exception in the hosting process,
1074 // which contains the associated AdapterView.
1075 Log.e(TAG, "Error: widget's RemoteViewsFactory returns more view types than " +
1076 " indicated by getViewTypeCount() ");
Adam Cohenb9673922012-01-05 13:58:47 -08001077 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001078 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001079 }
1080
Winson Chung9b3a2cf2010-09-16 14:45:32 -07001081 public Intent getRemoteViewsServiceIntent() {
1082 return mIntent;
1083 }
1084
Winson Chung499cb9f2010-07-16 11:18:17 -07001085 public int getCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001086 final RemoteViewsMetaData metaData = mCache.getMetaData();
1087 synchronized (metaData) {
1088 return metaData.count;
1089 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001090 }
1091
1092 public Object getItem(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001093 // Disallow arbitrary object to be associated with an item for the time being
Winson Chung499cb9f2010-07-16 11:18:17 -07001094 return null;
1095 }
1096
1097 public long getItemId(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001098 synchronized (mCache) {
1099 if (mCache.containsMetaDataAt(position)) {
1100 return mCache.getMetaDataAt(position).itemId;
1101 }
1102 return 0;
1103 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001104 }
1105
1106 public int getItemViewType(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001107 int typeId = 0;
1108 synchronized (mCache) {
1109 if (mCache.containsMetaDataAt(position)) {
1110 typeId = mCache.getMetaDataAt(position).typeId;
1111 } else {
1112 return 0;
1113 }
1114 }
1115
1116 final RemoteViewsMetaData metaData = mCache.getMetaData();
1117 synchronized (metaData) {
1118 return metaData.getMappedViewType(typeId);
1119 }
1120 }
1121
1122 /**
1123 * Returns the item type id for the specified convert view. Returns -1 if the convert view
1124 * is invalid.
1125 */
1126 private int getConvertViewTypeId(View convertView) {
1127 int typeId = -1;
Adam Cohena32edd42010-10-26 10:35:01 -07001128 if (convertView != null) {
1129 Object tag = convertView.getTag(com.android.internal.R.id.rowTypeId);
1130 if (tag != null) {
1131 typeId = (Integer) tag;
1132 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001133 }
1134 return typeId;
Winson Chung499cb9f2010-07-16 11:18:17 -07001135 }
1136
Adam Cohenb9673922012-01-05 13:58:47 -08001137 /**
1138 * This method allows an AdapterView using this Adapter to provide information about which
1139 * views are currently being displayed. This allows for certain optimizations and preloading
1140 * which wouldn't otherwise be possible.
1141 */
1142 public void setVisibleRangeHint(int lowerBound, int upperBound) {
1143 mVisibleWindowLowerBound = lowerBound;
1144 mVisibleWindowUpperBound = upperBound;
1145 }
1146
Winson Chung499cb9f2010-07-16 11:18:17 -07001147 public View getView(int position, View convertView, ViewGroup parent) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001148 // "Request" an index so that we can queue it for loading, initiate subsequent
1149 // preloading, etc.
1150 synchronized (mCache) {
1151 boolean isInCache = mCache.containsRemoteViewAt(position);
1152 boolean isConnected = mServiceConnection.isConnected();
1153 boolean hasNewItems = false;
1154
Adam Cohenff067192012-12-05 18:14:39 -08001155 if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
1156 mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
1157 }
1158
Winson Chung7ab73e72011-03-04 15:47:23 -08001159 if (!isInCache && !isConnected) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001160 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
1161 // in turn trigger another request to getView()
1162 requestBindService();
1163 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -07001164 // Queue up other indices to be preloaded based on this position
Winson Chung16c8d8a2011-01-20 16:19:33 -08001165 hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
1166 }
1167
1168 if (isInCache) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001169 View convertViewChild = null;
1170 int convertViewTypeId = 0;
Adam Cohen181d2e32011-01-17 12:40:29 -08001171 RemoteViewsFrameLayout layout = null;
1172
1173 if (convertView instanceof RemoteViewsFrameLayout) {
1174 layout = (RemoteViewsFrameLayout) convertView;
Winson Chung3ec9a452010-09-23 16:40:28 -07001175 convertViewChild = layout.getChildAt(0);
1176 convertViewTypeId = getConvertViewTypeId(convertViewChild);
1177 }
1178
1179 // Second, we try and retrieve the RemoteViews from the cache, returning a loading
1180 // view and queueing it to be loaded if it has not already been loaded.
Winson Chung16c8d8a2011-01-20 16:19:33 -08001181 Context context = parent.getContext();
1182 RemoteViews rv = mCache.getRemoteViewsAt(position);
Adam Cohenaeb66ca2011-02-10 16:08:12 -08001183 RemoteViewsIndexMetaData indexMetaData = mCache.getMetaDataAt(position);
Adam Cohenaeb66ca2011-02-10 16:08:12 -08001184 int typeId = indexMetaData.typeId;
Winson Chung3ec9a452010-09-23 16:40:28 -07001185
Adam Cohenb7ffea62011-07-14 14:45:07 -07001186 try {
1187 // Reuse the convert view where possible
1188 if (layout != null) {
1189 if (convertViewTypeId == typeId) {
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001190 rv.reapply(context, convertViewChild, mRemoteViewsOnClickHandler);
Adam Cohenb7ffea62011-07-14 14:45:07 -07001191 return layout;
1192 }
1193 layout.removeAllViews();
1194 } else {
1195 layout = new RemoteViewsFrameLayout(context);
Winson Chung3ec9a452010-09-23 16:40:28 -07001196 }
Adam Cohenb7ffea62011-07-14 14:45:07 -07001197
1198 // Otherwise, create a new view to be returned
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001199 View newView = rv.apply(context, parent, mRemoteViewsOnClickHandler);
Adam Cohenb7ffea62011-07-14 14:45:07 -07001200 newView.setTagInternal(com.android.internal.R.id.rowTypeId,
1201 new Integer(typeId));
1202 layout.addView(newView);
1203 return layout;
1204
1205 } catch (Exception e){
1206 // We have to make sure that we successfully inflated the RemoteViews, if not
1207 // we return the loading view instead.
1208 Log.w(TAG, "Error inflating RemoteViews at position: " + position + ", using" +
1209 "loading view instead" + e);
1210
1211 RemoteViewsFrameLayout loadingView = null;
1212 final RemoteViewsMetaData metaData = mCache.getMetaData();
1213 synchronized (metaData) {
Adam Cohena5a06872012-07-11 15:23:10 -07001214 loadingView = metaData.createLoadingView(position, convertView, parent,
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001215 mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
Adam Cohenb7ffea62011-07-14 14:45:07 -07001216 }
1217 return loadingView;
1218 } finally {
1219 if (hasNewItems) loadNextIndexInBackground();
Winson Chung3ec9a452010-09-23 16:40:28 -07001220 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08001221 } else {
1222 // If the cache does not have the RemoteViews at this position, then create a
1223 // loading view and queue the actual position to be loaded in the background
1224 RemoteViewsFrameLayout loadingView = null;
1225 final RemoteViewsMetaData metaData = mCache.getMetaData();
1226 synchronized (metaData) {
Adam Cohena5a06872012-07-11 15:23:10 -07001227 loadingView = metaData.createLoadingView(position, convertView, parent,
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001228 mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
Winson Chung16c8d8a2011-01-20 16:19:33 -08001229 }
1230
1231 mRequestedViews.add(position, loadingView);
1232 mCache.queueRequestedPositionToLoad(position);
1233 loadNextIndexInBackground();
1234
1235 return loadingView;
Winson Chung3ec9a452010-09-23 16:40:28 -07001236 }
1237 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001238 }
1239
1240 public int getViewTypeCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001241 final RemoteViewsMetaData metaData = mCache.getMetaData();
1242 synchronized (metaData) {
1243 return metaData.viewTypeCount;
1244 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001245 }
1246
1247 public boolean hasStableIds() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001248 final RemoteViewsMetaData metaData = mCache.getMetaData();
1249 synchronized (metaData) {
1250 return metaData.hasStableIds;
1251 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001252 }
1253
1254 public boolean isEmpty() {
1255 return getCount() <= 0;
1256 }
1257
Winson Chung16c8d8a2011-01-20 16:19:33 -08001258 private void onNotifyDataSetChanged() {
1259 // Complete the actual notifyDataSetChanged() call initiated earlier
1260 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
1261 try {
1262 factory.onDataSetChanged();
Adam Cohen2625fea2011-03-23 17:24:30 -07001263 } catch (RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001264 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
1265
1266 // Return early to prevent from further being notified (since nothing has
1267 // changed)
1268 return;
Adam Cohen2625fea2011-03-23 17:24:30 -07001269 } catch (RuntimeException e) {
1270 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
1271 return;
Winson Chung16c8d8a2011-01-20 16:19:33 -08001272 }
1273
1274 // Flush the cache so that we can reload new items from the service
1275 synchronized (mCache) {
1276 mCache.reset();
1277 }
1278
1279 // Re-request the new metadata (only after the notification to the factory)
1280 updateTemporaryMetaData();
Adam Cohen4c994982012-04-02 13:30:32 -07001281 int newCount;
Adam Cohen591ff972012-07-24 22:46:11 -07001282 ArrayList<Integer> visibleWindow;
Adam Cohen4c994982012-04-02 13:30:32 -07001283 synchronized(mCache.getTemporaryMetaData()) {
1284 newCount = mCache.getTemporaryMetaData().count;
Adam Cohen591ff972012-07-24 22:46:11 -07001285 visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
1286 mVisibleWindowUpperBound, newCount);
Adam Cohen4c994982012-04-02 13:30:32 -07001287 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08001288
Adam Cohenb9673922012-01-05 13:58:47 -08001289 // Pre-load (our best guess of) the views which are currently visible in the AdapterView.
1290 // This mitigates flashing and flickering of loading views when a widget notifies that
1291 // its data has changed.
Adam Cohen591ff972012-07-24 22:46:11 -07001292 for (int i: visibleWindow) {
Adam Cohen4c994982012-04-02 13:30:32 -07001293 // Because temporary meta data is only ever modified from this thread (ie.
1294 // mWorkerThread), it is safe to assume that count is a valid representation.
1295 if (i < newCount) {
Adam Cohen591ff972012-07-24 22:46:11 -07001296 updateRemoteViews(i, false);
Adam Cohen4c994982012-04-02 13:30:32 -07001297 }
Adam Cohenb9673922012-01-05 13:58:47 -08001298 }
1299
Winson Chung16c8d8a2011-01-20 16:19:33 -08001300 // Propagate the notification back to the base adapter
1301 mMainQueue.post(new Runnable() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001302 @Override
1303 public void run() {
Winson Chung6364f2b2010-09-29 11:14:30 -07001304 synchronized (mCache) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001305 mCache.commitTemporaryMetaData();
Winson Chung6364f2b2010-09-29 11:14:30 -07001306 }
1307
Winson Chung16c8d8a2011-01-20 16:19:33 -08001308 superNotifyDataSetChanged();
1309 enqueueDeferredUnbindServiceMessage();
Winson Chung3ec9a452010-09-23 16:40:28 -07001310 }
1311 });
Winson Chung6364f2b2010-09-29 11:14:30 -07001312
Winson Chung16c8d8a2011-01-20 16:19:33 -08001313 // Reset the notify flagflag
1314 mNotifyDataSetChangedAfterOnServiceConnected = false;
1315 }
1316
Adam Cohen591ff972012-07-24 22:46:11 -07001317 private ArrayList<Integer> getVisibleWindow(int lower, int upper, int count) {
1318 ArrayList<Integer> window = new ArrayList<Integer>();
Adam Cohen4a9df8d2012-07-27 15:35:13 -07001319
1320 // In the case that the window is invalid or uninitialized, return an empty window.
1321 if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
1322 return window;
1323 }
1324
Adam Cohen591ff972012-07-24 22:46:11 -07001325 if (lower <= upper) {
1326 for (int i = lower; i <= upper; i++){
1327 window.add(i);
1328 }
1329 } else {
1330 // If the upper bound is less than the lower bound it means that the visible window
1331 // wraps around.
1332 for (int i = lower; i < count; i++) {
1333 window.add(i);
1334 }
1335 for (int i = 0; i <= upper; i++) {
1336 window.add(i);
1337 }
1338 }
1339 return window;
1340 }
1341
Winson Chung16c8d8a2011-01-20 16:19:33 -08001342 public void notifyDataSetChanged() {
1343 // Dequeue any unbind messages
1344 mMainQueue.removeMessages(sUnbindServiceMessageType);
1345
1346 // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
1347 // connect
1348 if (!mServiceConnection.isConnected()) {
1349 if (mNotifyDataSetChangedAfterOnServiceConnected) {
1350 return;
1351 }
1352
1353 mNotifyDataSetChangedAfterOnServiceConnected = true;
1354 requestBindService();
1355 return;
1356 }
1357
1358 mWorkerQueue.post(new Runnable() {
1359 @Override
1360 public void run() {
1361 onNotifyDataSetChanged();
1362 }
1363 });
Winson Chung3ec9a452010-09-23 16:40:28 -07001364 }
1365
Adam Cohenfb603862010-12-17 12:03:17 -08001366 void superNotifyDataSetChanged() {
Winson Chung499cb9f2010-07-16 11:18:17 -07001367 super.notifyDataSetChanged();
1368 }
1369
Winson Chung81f39eb2011-01-11 18:05:01 -08001370 @Override
1371 public boolean handleMessage(Message msg) {
1372 boolean result = false;
1373 switch (msg.what) {
1374 case sUnbindServiceMessageType:
Winson Chung81f39eb2011-01-11 18:05:01 -08001375 if (mServiceConnection.isConnected()) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001376 mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
Winson Chung81f39eb2011-01-11 18:05:01 -08001377 }
1378 result = true;
1379 break;
1380 default:
1381 break;
1382 }
1383 return result;
1384 }
1385
1386 private void enqueueDeferredUnbindServiceMessage() {
1387 // Remove any existing deferred-unbind messages
1388 mMainQueue.removeMessages(sUnbindServiceMessageType);
1389 mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
1390 }
1391
Winson Chung499cb9f2010-07-16 11:18:17 -07001392 private boolean requestBindService() {
Winson Chung81f39eb2011-01-11 18:05:01 -08001393 // Try binding the service (which will start it if it's not already running)
Winson Chung499cb9f2010-07-16 11:18:17 -07001394 if (!mServiceConnection.isConnected()) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001395 mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
Winson Chung499cb9f2010-07-16 11:18:17 -07001396 }
1397
Winson Chung16c8d8a2011-01-20 16:19:33 -08001398 // Remove any existing deferred-unbind messages
1399 mMainQueue.removeMessages(sUnbindServiceMessageType);
Winson Chung499cb9f2010-07-16 11:18:17 -07001400 return mServiceConnection.isConnected();
1401 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001402}