blob: 3ff0ceee8f40d0cd2c0ee4df52bcf3192f4deeab [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
Jim Miller3e510532013-03-05 16:24:45 -080025import android.Manifest;
Winson Chung81f39eb2011-01-11 18:05:01 -080026import android.appwidget.AppWidgetManager;
Winson Chung499cb9f2010-07-16 11:18:17 -070027import android.content.Context;
28import android.content.Intent;
Jim Miller3e510532013-03-05 16:24:45 -080029import android.content.pm.PackageManager;
Winson Chung499cb9f2010-07-16 11:18:17 -070030import android.os.Handler;
31import android.os.HandlerThread;
32import android.os.IBinder;
33import android.os.Looper;
Winson Chung81f39eb2011-01-11 18:05:01 -080034import android.os.Message;
Amith Yamasanic566b432012-11-30 15:26:21 -080035import android.os.Process;
Adam Cohen2625fea2011-03-23 17:24:30 -070036import android.os.RemoteException;
Amith Yamasanic566b432012-11-30 15:26:21 -080037import android.os.UserHandle;
Winson Chungfbc35902010-09-09 16:45:06 -070038import android.util.Log;
Jim Miller3e510532013-03-05 16:24:45 -080039import android.util.Slog;
Winson Chunga5f6f802010-09-29 09:24:21 -070040import android.view.LayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070041import android.view.View;
Adam Cohen181d2e32011-01-17 12:40:29 -080042import android.view.View.MeasureSpec;
Winson Chung84bbb022011-02-21 13:57:45 -080043import android.view.ViewGroup;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070044import android.widget.RemoteViews.OnClickHandler;
Winson Chung499cb9f2010-07-16 11:18:17 -070045
Winson Chung81f39eb2011-01-11 18:05:01 -080046import com.android.internal.widget.IRemoteViewsAdapterConnection;
Winson Chung499cb9f2010-07-16 11:18:17 -070047import com.android.internal.widget.IRemoteViewsFactory;
Amith Yamasanic566b432012-11-30 15:26:21 -080048import com.android.internal.widget.LockPatternUtils;
Winson Chung499cb9f2010-07-16 11:18:17 -070049
50/**
51 * An adapter to a RemoteViewsService which fetches and caches RemoteViews
52 * to be later inflated as child views.
53 */
54/** @hide */
Winson Chung81f39eb2011-01-11 18:05:01 -080055public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
Jim Miller3e510532013-03-05 16:24:45 -080056 private static final String MULTI_USER_PERM = Manifest.permission.INTERACT_ACROSS_USERS_FULL;
57
Winson Chungfbc35902010-09-09 16:45:06 -070058 private static final String TAG = "RemoteViewsAdapter";
Winson Chung499cb9f2010-07-16 11:18:17 -070059
Jim Miller3e510532013-03-05 16:24:45 -080060 // The max number of items in the cache
Winson Chungb90a91c2011-01-26 13:36:34 -080061 private static final int sDefaultCacheSize = 40;
Winson Chung81f39eb2011-01-11 18:05:01 -080062 // The delay (in millis) to wait until attempting to unbind from a service after a request.
63 // This ensures that we don't stay continually bound to the service and that it can be destroyed
64 // if we need the memory elsewhere in the system.
Winson Chungb90a91c2011-01-26 13:36:34 -080065 private static final int sUnbindServiceDelay = 5000;
Adam Cohenb7ffea62011-07-14 14:45:07 -070066
67 // Default height for the default loading view, in case we cannot get inflate the first view
68 private static final int sDefaultLoadingViewHeight = 50;
69
Winson Chung81f39eb2011-01-11 18:05:01 -080070 // Type defs for controlling different messages across the main and worker message queues
Jim Miller3e510532013-03-05 16:24:45 -080071 private static final int sDefaultMessageType = 0;
Winson Chung81f39eb2011-01-11 18:05:01 -080072 private static final int sUnbindServiceMessageType = 1;
73
74 private final Context mContext;
75 private final Intent mIntent;
76 private final int mAppWidgetId;
Winson Chunga5f6f802010-09-29 09:24:21 -070077 private LayoutInflater mLayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070078 private RemoteViewsAdapterServiceConnection mServiceConnection;
Winson Chung3ec9a452010-09-23 16:40:28 -070079 private WeakReference<RemoteAdapterConnectionCallback> mCallback;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070080 private OnClickHandler mRemoteViewsOnClickHandler;
Winson Chung3ec9a452010-09-23 16:40:28 -070081 private FixedSizeRemoteViewsCache mCache;
Adam Cohenb9673922012-01-05 13:58:47 -080082 private int mVisibleWindowLowerBound;
83 private int mVisibleWindowUpperBound;
Winson Chung3ec9a452010-09-23 16:40:28 -070084
Winson Chung16c8d8a2011-01-20 16:19:33 -080085 // A flag to determine whether we should notify data set changed after we connect
86 private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
87
Winson Chung3ec9a452010-09-23 16:40:28 -070088 // The set of requested views that are to be notified when the associated RemoteViews are
89 // loaded.
90 private RemoteViewsFrameLayoutRefSet mRequestedViews;
Winson Chung499cb9f2010-07-16 11:18:17 -070091
92 private HandlerThread mWorkerThread;
93 // items may be interrupted within the normally processed queues
94 private Handler mWorkerQueue;
95 private Handler mMainQueue;
Winson Chung499cb9f2010-07-16 11:18:17 -070096
Adam Cohen335c3b62012-07-24 17:18:16 -070097 // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
Jim Miller3e510532013-03-05 16:24:45 -080098 // structures;
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -080099 private static final HashMap<RemoteViewsCacheKey,
100 FixedSizeRemoteViewsCache> sCachedRemoteViewsCaches
101 = new HashMap<RemoteViewsCacheKey,
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700102 FixedSizeRemoteViewsCache>();
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800103 private static final HashMap<RemoteViewsCacheKey, Runnable>
104 sRemoteViewsCacheRemoveRunnables
105 = new HashMap<RemoteViewsCacheKey, Runnable>();
106
Adam Cohen335c3b62012-07-24 17:18:16 -0700107 private static HandlerThread sCacheRemovalThread;
108 private static Handler sCacheRemovalQueue;
109
110 // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation.
111 // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this
112 // duration, the cache is dropped.
113 private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
114
115 // Used to indicate to the AdapterView that it can use this Adapter immediately after
116 // construction (happens when we have a cached FixedSizeRemoteViewsCache).
117 private boolean mDataReady = false;
118
Amith Yamasanic566b432012-11-30 15:26:21 -0800119 int mUserId;
120
Winson Chung499cb9f2010-07-16 11:18:17 -0700121 /**
122 * An interface for the RemoteAdapter to notify other classes when adapters
123 * are actually connected to/disconnected from their actual services.
124 */
125 public interface RemoteAdapterConnectionCallback {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800126 /**
127 * @return whether the adapter was set or not.
128 */
129 public boolean onRemoteAdapterConnected();
Winson Chung499cb9f2010-07-16 11:18:17 -0700130
131 public void onRemoteAdapterDisconnected();
Adam Cohen2148d432011-07-28 14:59:54 -0700132
133 /**
134 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
135 * connected yet.
136 */
137 public void deferNotifyDataSetChanged();
Winson Chung499cb9f2010-07-16 11:18:17 -0700138 }
139
140 /**
141 * The service connection that gets populated when the RemoteViewsService is
Winson Chung3ec9a452010-09-23 16:40:28 -0700142 * bound. This must be a static inner class to ensure that no references to the outer
143 * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
144 * garbage collected, and would cause us to leak activities due to the caching mechanism for
145 * FrameLayouts in the adapter).
Winson Chung499cb9f2010-07-16 11:18:17 -0700146 */
Winson Chung81f39eb2011-01-11 18:05:01 -0800147 private static class RemoteViewsAdapterServiceConnection extends
148 IRemoteViewsAdapterConnection.Stub {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800149 private boolean mIsConnected;
150 private boolean mIsConnecting;
Winson Chung3ec9a452010-09-23 16:40:28 -0700151 private WeakReference<RemoteViewsAdapter> mAdapter;
Winson Chung499cb9f2010-07-16 11:18:17 -0700152 private IRemoteViewsFactory mRemoteViewsFactory;
Winson Chung499cb9f2010-07-16 11:18:17 -0700153
Winson Chung3ec9a452010-09-23 16:40:28 -0700154 public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
155 mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
Winson Chung499cb9f2010-07-16 11:18:17 -0700156 }
157
Winson Chung16c8d8a2011-01-20 16:19:33 -0800158 public synchronized void bind(Context context, int appWidgetId, Intent intent) {
159 if (!mIsConnecting) {
160 try {
Amith Yamasanic566b432012-11-30 15:26:21 -0800161 RemoteViewsAdapter adapter;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800162 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
Jim Miller3e510532013-03-05 16:24:45 -0800163 if ((adapter = mAdapter.get()) != null) {
164 checkInteractAcrossUsersPermission(context, adapter.mUserId);
Amith Yamasanic566b432012-11-30 15:26:21 -0800165 mgr.bindRemoteViewsService(appWidgetId, intent, asBinder(),
166 new UserHandle(adapter.mUserId));
167 } else {
Jim Miller3e510532013-03-05 16:24:45 -0800168 Slog.w(TAG, "bind: adapter was null");
Amith Yamasanic566b432012-11-30 15:26:21 -0800169 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800170 mIsConnecting = true;
171 } catch (Exception e) {
172 Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
173 mIsConnecting = false;
174 mIsConnected = false;
175 }
176 }
177 }
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700178
Winson Chung16c8d8a2011-01-20 16:19:33 -0800179 public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
180 try {
Amith Yamasanic566b432012-11-30 15:26:21 -0800181 RemoteViewsAdapter adapter;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800182 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
Jim Miller3e510532013-03-05 16:24:45 -0800183 if ((adapter = mAdapter.get()) != null) {
184 checkInteractAcrossUsersPermission(context, adapter.mUserId);
Amith Yamasanic566b432012-11-30 15:26:21 -0800185 mgr.unbindRemoteViewsService(appWidgetId, intent,
186 new UserHandle(adapter.mUserId));
187 } else {
Jim Miller3e510532013-03-05 16:24:45 -0800188 Slog.w(TAG, "unbind: adapter was null");
Amith Yamasanic566b432012-11-30 15:26:21 -0800189 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800190 mIsConnecting = false;
191 } catch (Exception e) {
192 Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
193 mIsConnecting = false;
194 mIsConnected = false;
195 }
196 }
197
198 public synchronized void onServiceConnected(IBinder service) {
199 mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
200
201 // Remove any deferred unbind messages
Winson Chung3ec9a452010-09-23 16:40:28 -0700202 final RemoteViewsAdapter adapter = mAdapter.get();
203 if (adapter == null) return;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800204
205 // Queue up work that we need to do for the callback to run
Winson Chung3ec9a452010-09-23 16:40:28 -0700206 adapter.mWorkerQueue.post(new Runnable() {
Winson Chung499cb9f2010-07-16 11:18:17 -0700207 @Override
208 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800209 if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
210 // Handle queued notifyDataSetChanged() if necessary
211 adapter.onNotifyDataSetChanged();
212 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700213 IRemoteViewsFactory factory =
214 adapter.mServiceConnection.getRemoteViewsFactory();
215 try {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800216 if (!factory.isCreated()) {
217 // We only call onDataSetChanged() if this is the factory was just
218 // create in response to this bind
219 factory.onDataSetChanged();
220 }
Adam Cohen2625fea2011-03-23 17:24:30 -0700221 } catch (RemoteException e) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700222 Log.e(TAG, "Error notifying factory of data set changed in " +
223 "onServiceConnected(): " + e.getMessage());
Winson Chung499cb9f2010-07-16 11:18:17 -0700224
Winson Chung3ec9a452010-09-23 16:40:28 -0700225 // Return early to prevent anything further from being notified
226 // (effectively nothing has changed)
227 return;
Adam Cohen2625fea2011-03-23 17:24:30 -0700228 } catch (RuntimeException e) {
229 Log.e(TAG, "Error notifying factory of data set changed in " +
230 "onServiceConnected(): " + e.getMessage());
Winson Chung499cb9f2010-07-16 11:18:17 -0700231 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700232
233 // Request meta data so that we have up to date data when calling back to
234 // the remote adapter callback
Winson Chung16c8d8a2011-01-20 16:19:33 -0800235 adapter.updateTemporaryMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700236
Winson Chung16c8d8a2011-01-20 16:19:33 -0800237 // Notify the host that we've connected
Winson Chung61ac7e32010-09-28 10:08:31 -0700238 adapter.mMainQueue.post(new Runnable() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700239 @Override
240 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800241 synchronized (adapter.mCache) {
242 adapter.mCache.commitTemporaryMetaData();
243 }
244
Winson Chung3ec9a452010-09-23 16:40:28 -0700245 final RemoteAdapterConnectionCallback callback =
246 adapter.mCallback.get();
247 if (callback != null) {
248 callback.onRemoteAdapterConnected();
249 }
250 }
251 });
252 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800253
254 // Enqueue unbind message
255 adapter.enqueueDeferredUnbindServiceMessage();
256 mIsConnected = true;
257 mIsConnecting = false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700258 }
259 });
260 }
261
Winson Chung16c8d8a2011-01-20 16:19:33 -0800262 public synchronized void onServiceDisconnected() {
263 mIsConnected = false;
264 mIsConnecting = false;
Winson Chung3ec9a452010-09-23 16:40:28 -0700265 mRemoteViewsFactory = null;
Winson Chung499cb9f2010-07-16 11:18:17 -0700266
Winson Chung16c8d8a2011-01-20 16:19:33 -0800267 // Clear the main/worker queues
Winson Chung3ec9a452010-09-23 16:40:28 -0700268 final RemoteViewsAdapter adapter = mAdapter.get();
269 if (adapter == null) return;
Jim Miller3e510532013-03-05 16:24:45 -0800270
Winson Chung16c8d8a2011-01-20 16:19:33 -0800271 adapter.mMainQueue.post(new Runnable() {
272 @Override
273 public void run() {
274 // Dequeue any unbind messages
275 adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700276
Winson Chung16c8d8a2011-01-20 16:19:33 -0800277 final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
278 if (callback != null) {
279 callback.onRemoteAdapterDisconnected();
280 }
281 }
282 });
Winson Chung499cb9f2010-07-16 11:18:17 -0700283 }
284
Winson Chung16c8d8a2011-01-20 16:19:33 -0800285 public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
Winson Chung499cb9f2010-07-16 11:18:17 -0700286 return mRemoteViewsFactory;
287 }
288
Winson Chung16c8d8a2011-01-20 16:19:33 -0800289 public synchronized boolean isConnected() {
290 return mIsConnected;
Winson Chung499cb9f2010-07-16 11:18:17 -0700291 }
292 }
293
294 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700295 * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
296 * they are loaded.
Winson Chung499cb9f2010-07-16 11:18:17 -0700297 */
Adam Cohena5a06872012-07-11 15:23:10 -0700298 private static class RemoteViewsFrameLayout extends FrameLayout {
Winson Chung3ec9a452010-09-23 16:40:28 -0700299 public RemoteViewsFrameLayout(Context context) {
300 super(context);
Winson Chung499cb9f2010-07-16 11:18:17 -0700301 }
302
303 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700304 * Updates this RemoteViewsFrameLayout depending on the view that was loaded.
305 * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
306 * successfully.
Winson Chung499cb9f2010-07-16 11:18:17 -0700307 */
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700308 public void onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700309 try {
310 // Remove all the children of this layout first
311 removeAllViews();
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700312 addView(view.apply(getContext(), this, handler));
Winson Chung61ac7e32010-09-28 10:08:31 -0700313 } catch (Exception e) {
314 Log.e(TAG, "Failed to apply RemoteViews.");
315 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700316 }
317 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700318
Winson Chung3ec9a452010-09-23 16:40:28 -0700319 /**
320 * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
321 * adapter that have not yet had their RemoteViews loaded.
322 */
323 private class RemoteViewsFrameLayoutRefSet {
324 private HashMap<Integer, LinkedList<RemoteViewsFrameLayout>> mReferences;
Adam Cohenff067192012-12-05 18:14:39 -0800325 private HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>
326 mViewToLinkedList;
Winson Chung3ec9a452010-09-23 16:40:28 -0700327
328 public RemoteViewsFrameLayoutRefSet() {
329 mReferences = new HashMap<Integer, LinkedList<RemoteViewsFrameLayout>>();
Adam Cohenff067192012-12-05 18:14:39 -0800330 mViewToLinkedList =
331 new HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>();
Winson Chung3ec9a452010-09-23 16:40:28 -0700332 }
333
334 /**
335 * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
336 */
337 public void add(int position, RemoteViewsFrameLayout layout) {
338 final Integer pos = position;
339 LinkedList<RemoteViewsFrameLayout> refs;
340
341 // Create the list if necessary
342 if (mReferences.containsKey(pos)) {
343 refs = mReferences.get(pos);
344 } else {
345 refs = new LinkedList<RemoteViewsFrameLayout>();
346 mReferences.put(pos, refs);
Winson Chung499cb9f2010-07-16 11:18:17 -0700347 }
Adam Cohenff067192012-12-05 18:14:39 -0800348 mViewToLinkedList.put(layout, refs);
Winson Chung3ec9a452010-09-23 16:40:28 -0700349
350 // Add the references to the list
351 refs.add(layout);
Winson Chung499cb9f2010-07-16 11:18:17 -0700352 }
353
Winson Chung3ec9a452010-09-23 16:40:28 -0700354 /**
355 * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
356 * the associated RemoteViews has loaded.
357 */
Adam Cohena5a06872012-07-11 15:23:10 -0700358 public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700359 if (view == null) return;
360
Winson Chung3ec9a452010-09-23 16:40:28 -0700361 final Integer pos = position;
362 if (mReferences.containsKey(pos)) {
363 // Notify all the references for that position of the newly loaded RemoteViews
364 final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos);
365 for (final RemoteViewsFrameLayout ref : refs) {
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700366 ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler);
Adam Cohenff067192012-12-05 18:14:39 -0800367 if (mViewToLinkedList.containsKey(ref)) {
368 mViewToLinkedList.remove(ref);
369 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700370 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700371 refs.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700372 // Remove this set from the original mapping
373 mReferences.remove(pos);
Winson Chung499cb9f2010-07-16 11:18:17 -0700374 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700375 }
376
Winson Chung3ec9a452010-09-23 16:40:28 -0700377 /**
Adam Cohenff067192012-12-05 18:14:39 -0800378 * We need to remove views from this set if they have been recycled by the AdapterView.
379 */
380 public void removeView(RemoteViewsFrameLayout rvfl) {
381 if (mViewToLinkedList.containsKey(rvfl)) {
382 mViewToLinkedList.get(rvfl).remove(rvfl);
383 mViewToLinkedList.remove(rvfl);
384 }
385 }
386
387 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700388 * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
389 */
390 public void clear() {
391 // We currently just clear the references, and leave all the previous layouts returned
392 // in their default state of the loading view.
393 mReferences.clear();
Adam Cohenff067192012-12-05 18:14:39 -0800394 mViewToLinkedList.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700395 }
396 }
397
398 /**
399 * The meta-data associated with the cache in it's current state.
400 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700401 private static class RemoteViewsMetaData {
Winson Chung3ec9a452010-09-23 16:40:28 -0700402 int count;
403 int viewTypeCount;
404 boolean hasStableIds;
Winson Chung3ec9a452010-09-23 16:40:28 -0700405
406 // Used to determine how to construct loading views. If a loading view is not specified
407 // by the user, then we try and load the first view, and use its height as the height for
408 // the default loading view.
409 RemoteViews mUserLoadingView;
410 RemoteViews mFirstView;
411 int mFirstViewHeight;
412
413 // A mapping from type id to a set of unique type ids
Winson Chung16c8d8a2011-01-20 16:19:33 -0800414 private final HashMap<Integer, Integer> mTypeIdIndexMap = new HashMap<Integer, Integer>();
Winson Chung3ec9a452010-09-23 16:40:28 -0700415
416 public RemoteViewsMetaData() {
417 reset();
Winson Chung499cb9f2010-07-16 11:18:17 -0700418 }
419
Winson Chung16c8d8a2011-01-20 16:19:33 -0800420 public void set(RemoteViewsMetaData d) {
421 synchronized (d) {
422 count = d.count;
423 viewTypeCount = d.viewTypeCount;
424 hasStableIds = d.hasStableIds;
425 setLoadingViewTemplates(d.mUserLoadingView, d.mFirstView);
426 }
427 }
428
Winson Chung3ec9a452010-09-23 16:40:28 -0700429 public void reset() {
430 count = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800431
Winson Chung3ec9a452010-09-23 16:40:28 -0700432 // by default there is at least one dummy view type
433 viewTypeCount = 1;
434 hasStableIds = true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700435 mUserLoadingView = null;
436 mFirstView = null;
437 mFirstViewHeight = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800438 mTypeIdIndexMap.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700439 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700440
Winson Chung3ec9a452010-09-23 16:40:28 -0700441 public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
442 mUserLoadingView = loadingView;
443 if (firstView != null) {
444 mFirstView = firstView;
445 mFirstViewHeight = -1;
446 }
447 }
Winson Chungfbc35902010-09-09 16:45:06 -0700448
Winson Chung3ec9a452010-09-23 16:40:28 -0700449 public int getMappedViewType(int typeId) {
450 if (mTypeIdIndexMap.containsKey(typeId)) {
451 return mTypeIdIndexMap.get(typeId);
452 } else {
453 // We +1 because the loading view always has view type id of 0
454 int incrementalTypeId = mTypeIdIndexMap.size() + 1;
455 mTypeIdIndexMap.put(typeId, incrementalTypeId);
456 return incrementalTypeId;
457 }
458 }
459
Adam Cohena5a06872012-07-11 15:23:10 -0700460 public boolean isViewTypeInRange(int typeId) {
461 int mappedType = getMappedViewType(typeId);
462 if (mappedType >= viewTypeCount) {
463 return false;
464 } else {
465 return true;
466 }
467 }
468
Winson Chung3ec9a452010-09-23 16:40:28 -0700469 private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700470 ViewGroup parent, Object lock, LayoutInflater layoutInflater, OnClickHandler
471 handler) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700472 // Create and return a new FrameLayout, and setup the references for this position
473 final Context context = parent.getContext();
474 RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
475
476 // Create a new loading view
Adam Cohena5a06872012-07-11 15:23:10 -0700477 synchronized (lock) {
Adam Cohenb7ffea62011-07-14 14:45:07 -0700478 boolean customLoadingViewAvailable = false;
479
Winson Chung3ec9a452010-09-23 16:40:28 -0700480 if (mUserLoadingView != null) {
Adam Cohenb7ffea62011-07-14 14:45:07 -0700481 // Try to inflate user-specified loading view
482 try {
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700483 View loadingView = mUserLoadingView.apply(parent.getContext(), parent,
484 handler);
Adam Cohenb7ffea62011-07-14 14:45:07 -0700485 loadingView.setTagInternal(com.android.internal.R.id.rowTypeId,
486 new Integer(0));
487 layout.addView(loadingView);
488 customLoadingViewAvailable = true;
489 } catch (Exception e) {
490 Log.w(TAG, "Error inflating custom loading view, using default loading" +
491 "view instead", e);
492 }
493 }
494 if (!customLoadingViewAvailable) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700495 // A default loading view
496 // Use the size of the first row as a guide for the size of the loading view
497 if (mFirstViewHeight < 0) {
Adam Cohenb7ffea62011-07-14 14:45:07 -0700498 try {
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700499 View firstView = mFirstView.apply(parent.getContext(), parent, handler);
Adam Cohenb7ffea62011-07-14 14:45:07 -0700500 firstView.measure(
501 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
502 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
503 mFirstViewHeight = firstView.getMeasuredHeight();
504 mFirstView = null;
505 } catch (Exception e) {
Adam Cohena5a06872012-07-11 15:23:10 -0700506 float density = context.getResources().getDisplayMetrics().density;
Adam Cohenb7ffea62011-07-14 14:45:07 -0700507 mFirstViewHeight = (int)
508 Math.round(sDefaultLoadingViewHeight * density);
509 mFirstView = null;
510 Log.w(TAG, "Error inflating first RemoteViews" + e);
511 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700512 }
513
Winson Chung3ec9a452010-09-23 16:40:28 -0700514 // Compose the loading view text
Adam Cohena5a06872012-07-11 15:23:10 -0700515 TextView loadingTextView = (TextView) layoutInflater.inflate(
Adam Cohenfb603862010-12-17 12:03:17 -0800516 com.android.internal.R.layout.remote_views_adapter_default_loading_view,
517 layout, false);
Winson Chunga5f6f802010-09-29 09:24:21 -0700518 loadingTextView.setHeight(mFirstViewHeight);
519 loadingTextView.setTag(new Integer(0));
Winson Chung3ec9a452010-09-23 16:40:28 -0700520
Winson Chunga5f6f802010-09-29 09:24:21 -0700521 layout.addView(loadingTextView);
Winson Chung499cb9f2010-07-16 11:18:17 -0700522 }
523 }
524
Winson Chung3ec9a452010-09-23 16:40:28 -0700525 return layout;
526 }
527 }
528
529 /**
530 * The meta-data associated with a single item in the cache.
531 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700532 private static class RemoteViewsIndexMetaData {
Winson Chung3ec9a452010-09-23 16:40:28 -0700533 int typeId;
534 long itemId;
535
Adam Cohen591ff972012-07-24 22:46:11 -0700536 public RemoteViewsIndexMetaData(RemoteViews v, long itemId) {
537 set(v, itemId);
Winson Chung3ec9a452010-09-23 16:40:28 -0700538 }
539
Adam Cohen591ff972012-07-24 22:46:11 -0700540 public void set(RemoteViews v, long id) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700541 itemId = id;
Adam Cohena5a06872012-07-11 15:23:10 -0700542 if (v != null) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700543 typeId = v.getLayoutId();
Adam Cohena5a06872012-07-11 15:23:10 -0700544 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700545 typeId = 0;
Adam Cohena5a06872012-07-11 15:23:10 -0700546 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700547 }
548 }
549
550 /**
551 *
552 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700553 private static class FixedSizeRemoteViewsCache {
Winson Chung3ec9a452010-09-23 16:40:28 -0700554 private static final String TAG = "FixedSizeRemoteViewsCache";
555
556 // The meta data related to all the RemoteViews, ie. count, is stable, etc.
Adam Cohen4c994982012-04-02 13:30:32 -0700557 // The meta data objects are made final so that they can be locked on independently
558 // of the FixedSizeRemoteViewsCache. If we ever lock on both meta data objects, it is in
559 // the order mTemporaryMetaData followed by mMetaData.
560 private final RemoteViewsMetaData mMetaData;
561 private final RemoteViewsMetaData mTemporaryMetaData;
Winson Chung3ec9a452010-09-23 16:40:28 -0700562
563 // The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
564 // greater than or equal to the set of RemoteViews.
565 // Note: The reason that we keep this separate from the RemoteViews cache below is that this
566 // we still need to be able to access the mapping of position to meta data, without keeping
567 // the heavy RemoteViews around. The RemoteViews cache is trimmed to fixed constraints wrt.
568 // memory and size, but this metadata cache will retain information until the data at the
569 // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
570 private HashMap<Integer, RemoteViewsIndexMetaData> mIndexMetaData;
571
572 // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
573 // too much memory.
574 private HashMap<Integer, RemoteViews> mIndexRemoteViews;
575
576 // The set of indices that have been explicitly requested by the collection view
577 private HashSet<Integer> mRequestedIndices;
578
Winson Chungb90a91c2011-01-26 13:36:34 -0800579 // We keep a reference of the last requested index to determine which item to prune the
580 // farthest items from when we hit the memory limit
581 private int mLastRequestedIndex;
582
Winson Chung3ec9a452010-09-23 16:40:28 -0700583 // The set of indices to load, including those explicitly requested, as well as those
584 // determined by the preloading algorithm to be prefetched
585 private HashSet<Integer> mLoadIndices;
586
587 // The lower and upper bounds of the preloaded range
588 private int mPreloadLowerBound;
589 private int mPreloadUpperBound;
590
591 // The bounds of this fixed cache, we will try and fill as many items into the cache up to
592 // the maxCount number of items, or the maxSize memory usage.
593 // The maxCountSlack is used to determine if a new position in the cache to be loaded is
594 // sufficiently ouside the old set, prompting a shifting of the "window" of items to be
595 // preloaded.
596 private int mMaxCount;
597 private int mMaxCountSlack;
598 private static final float sMaxCountSlackPercent = 0.75f;
Winson Chungb90a91c2011-01-26 13:36:34 -0800599 private static final int sMaxMemoryLimitInBytes = 2 * 1024 * 1024;
Winson Chung3ec9a452010-09-23 16:40:28 -0700600
601 public FixedSizeRemoteViewsCache(int maxCacheSize) {
602 mMaxCount = maxCacheSize;
603 mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
604 mPreloadLowerBound = 0;
605 mPreloadUpperBound = -1;
606 mMetaData = new RemoteViewsMetaData();
Winson Chung16c8d8a2011-01-20 16:19:33 -0800607 mTemporaryMetaData = new RemoteViewsMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700608 mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
609 mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
610 mRequestedIndices = new HashSet<Integer>();
Winson Chungb90a91c2011-01-26 13:36:34 -0800611 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700612 mLoadIndices = new HashSet<Integer>();
613 }
614
Adam Cohen591ff972012-07-24 22:46:11 -0700615 public void insert(int position, RemoteViews v, long itemId,
616 ArrayList<Integer> visibleWindow) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700617 // Trim the cache if we go beyond the count
618 if (mIndexRemoteViews.size() >= mMaxCount) {
Adam Cohen591ff972012-07-24 22:46:11 -0700619 mIndexRemoteViews.remove(getFarthestPositionFrom(position, visibleWindow));
Winson Chung499cb9f2010-07-16 11:18:17 -0700620 }
621
Winson Chung3ec9a452010-09-23 16:40:28 -0700622 // Trim the cache if we go beyond the available memory size constraints
Winson Chungb90a91c2011-01-26 13:36:34 -0800623 int pruneFromPosition = (mLastRequestedIndex > -1) ? mLastRequestedIndex : position;
624 while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryLimitInBytes) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700625 // Note: This is currently the most naive mechanism for deciding what to prune when
626 // we hit the memory limit. In the future, we may want to calculate which index to
627 // remove based on both its position as well as it's current memory usage, as well
628 // as whether it was directly requested vs. whether it was preloaded by our caching
629 // mechanism.
Adam Cohen591ff972012-07-24 22:46:11 -0700630 mIndexRemoteViews.remove(getFarthestPositionFrom(pruneFromPosition, visibleWindow));
Winson Chung3ec9a452010-09-23 16:40:28 -0700631 }
632
633 // Update the metadata cache
634 if (mIndexMetaData.containsKey(position)) {
635 final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
Adam Cohen591ff972012-07-24 22:46:11 -0700636 metaData.set(v, itemId);
Winson Chung3ec9a452010-09-23 16:40:28 -0700637 } else {
Adam Cohen591ff972012-07-24 22:46:11 -0700638 mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId));
Winson Chung3ec9a452010-09-23 16:40:28 -0700639 }
640 mIndexRemoteViews.put(position, v);
641 }
642
643 public RemoteViewsMetaData getMetaData() {
644 return mMetaData;
645 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800646 public RemoteViewsMetaData getTemporaryMetaData() {
647 return mTemporaryMetaData;
648 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700649 public RemoteViews getRemoteViewsAt(int position) {
650 if (mIndexRemoteViews.containsKey(position)) {
651 return mIndexRemoteViews.get(position);
652 }
653 return null;
654 }
655 public RemoteViewsIndexMetaData getMetaDataAt(int position) {
656 if (mIndexMetaData.containsKey(position)) {
657 return mIndexMetaData.get(position);
658 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700659 return null;
660 }
661
Winson Chung16c8d8a2011-01-20 16:19:33 -0800662 public void commitTemporaryMetaData() {
663 synchronized (mTemporaryMetaData) {
664 synchronized (mMetaData) {
665 mMetaData.set(mTemporaryMetaData);
666 }
667 }
668 }
669
Winson Chung3ec9a452010-09-23 16:40:28 -0700670 private int getRemoteViewsBitmapMemoryUsage() {
671 // Calculate the memory usage of all the RemoteViews bitmaps being cached
672 int mem = 0;
673 for (Integer i : mIndexRemoteViews.keySet()) {
674 final RemoteViews v = mIndexRemoteViews.get(i);
Winson Chungaaffa8b2010-10-30 14:04:05 -0700675 if (v != null) {
Adam Cohen5d200642012-04-24 10:43:31 -0700676 mem += v.estimateMemoryUsage();
Winson Chungaaffa8b2010-10-30 14:04:05 -0700677 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700678 }
679 return mem;
680 }
Adam Cohen591ff972012-07-24 22:46:11 -0700681
682 private int getFarthestPositionFrom(int pos, ArrayList<Integer> visibleWindow) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700683 // Find the index farthest away and remove that
684 int maxDist = 0;
685 int maxDistIndex = -1;
Adam Cohen591ff972012-07-24 22:46:11 -0700686 int maxDistNotVisible = 0;
687 int maxDistIndexNotVisible = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700688 for (int i : mIndexRemoteViews.keySet()) {
689 int dist = Math.abs(i-pos);
Adam Cohen591ff972012-07-24 22:46:11 -0700690 if (dist > maxDistNotVisible && !visibleWindow.contains(i)) {
691 // maxDistNotVisible/maxDistIndexNotVisible will store the index of the
692 // farthest non-visible position
693 maxDistIndexNotVisible = i;
694 maxDistNotVisible = dist;
Winson Chungb90a91c2011-01-26 13:36:34 -0800695 }
Adam Cohen35fbe2a2012-05-22 14:10:14 -0700696 if (dist >= maxDist) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800697 // maxDist/maxDistIndex will store the index of the farthest position
Adam Cohen591ff972012-07-24 22:46:11 -0700698 // regardless of whether it is visible or not
Winson Chung3ec9a452010-09-23 16:40:28 -0700699 maxDistIndex = i;
700 maxDist = dist;
Winson Chung499cb9f2010-07-16 11:18:17 -0700701 }
702 }
Adam Cohen591ff972012-07-24 22:46:11 -0700703 if (maxDistIndexNotVisible > -1) {
704 return maxDistIndexNotVisible;
Winson Chungb90a91c2011-01-26 13:36:34 -0800705 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700706 return maxDistIndex;
Winson Chung499cb9f2010-07-16 11:18:17 -0700707 }
708
Winson Chung3ec9a452010-09-23 16:40:28 -0700709 public void queueRequestedPositionToLoad(int position) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800710 mLastRequestedIndex = position;
Winson Chung3ec9a452010-09-23 16:40:28 -0700711 synchronized (mLoadIndices) {
712 mRequestedIndices.add(position);
713 mLoadIndices.add(position);
714 }
715 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800716 public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700717 // Check if we need to preload any items
718 if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
719 int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
720 if (Math.abs(position - center) < mMaxCountSlack) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800721 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700722 }
723 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700724
Winson Chung3ec9a452010-09-23 16:40:28 -0700725 int count = 0;
726 synchronized (mMetaData) {
727 count = mMetaData.count;
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700728 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700729 synchronized (mLoadIndices) {
730 mLoadIndices.clear();
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700731
Winson Chung3ec9a452010-09-23 16:40:28 -0700732 // Add all the requested indices
733 mLoadIndices.addAll(mRequestedIndices);
Winson Chung499cb9f2010-07-16 11:18:17 -0700734
Winson Chung3ec9a452010-09-23 16:40:28 -0700735 // Add all the preload indices
736 int halfMaxCount = mMaxCount / 2;
737 mPreloadLowerBound = position - halfMaxCount;
738 mPreloadUpperBound = position + halfMaxCount;
739 int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
740 int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
741 for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
742 mLoadIndices.add(i);
Winson Chung499cb9f2010-07-16 11:18:17 -0700743 }
744
Winson Chung3ec9a452010-09-23 16:40:28 -0700745 // But remove all the indices that have already been loaded and are cached
746 mLoadIndices.removeAll(mIndexRemoteViews.keySet());
747 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800748 return true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700749 }
Winson Chungb90a91c2011-01-26 13:36:34 -0800750 /** Returns the next index to load, and whether that index was directly requested or not */
751 public int[] getNextIndexToLoad() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700752 // We try and prioritize items that have been requested directly, instead
753 // of items that are loaded as a result of the caching mechanism
754 synchronized (mLoadIndices) {
755 // Prioritize requested indices to be loaded first
756 if (!mRequestedIndices.isEmpty()) {
757 Integer i = mRequestedIndices.iterator().next();
758 mRequestedIndices.remove(i);
759 mLoadIndices.remove(i);
Winson Chungb90a91c2011-01-26 13:36:34 -0800760 return new int[]{i.intValue(), 1};
Winson Chung3ec9a452010-09-23 16:40:28 -0700761 }
762
763 // Otherwise, preload other indices as necessary
764 if (!mLoadIndices.isEmpty()) {
765 Integer i = mLoadIndices.iterator().next();
766 mLoadIndices.remove(i);
Winson Chungb90a91c2011-01-26 13:36:34 -0800767 return new int[]{i.intValue(), 0};
Winson Chung3ec9a452010-09-23 16:40:28 -0700768 }
769
Winson Chungb90a91c2011-01-26 13:36:34 -0800770 return new int[]{-1, 0};
Winson Chung3ec9a452010-09-23 16:40:28 -0700771 }
772 }
773
774 public boolean containsRemoteViewAt(int position) {
775 return mIndexRemoteViews.containsKey(position);
776 }
777 public boolean containsMetaDataAt(int position) {
778 return mIndexMetaData.containsKey(position);
779 }
780
781 public void reset() {
Winson Chung61ac7e32010-09-28 10:08:31 -0700782 // Note: We do not try and reset the meta data, since that information is still used by
783 // collection views to validate it's own contents (and will be re-requested if the data
784 // is invalidated through the notifyDataSetChanged() flow).
785
Winson Chung3ec9a452010-09-23 16:40:28 -0700786 mPreloadLowerBound = 0;
787 mPreloadUpperBound = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800788 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700789 mIndexRemoteViews.clear();
790 mIndexMetaData.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700791 synchronized (mLoadIndices) {
792 mRequestedIndices.clear();
793 mLoadIndices.clear();
Winson Chung499cb9f2010-07-16 11:18:17 -0700794 }
795 }
796 }
797
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800798 static class RemoteViewsCacheKey {
799 final Intent.FilterComparison filter;
800 final int widgetId;
801 final int userId;
802
803 RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId, int userId) {
804 this.filter = filter;
805 this.widgetId = widgetId;
806 this.userId = userId;
807 }
808
809 @Override
810 public boolean equals(Object o) {
811 if (!(o instanceof RemoteViewsCacheKey)) {
812 return false;
813 }
814 RemoteViewsCacheKey other = (RemoteViewsCacheKey) o;
815 return other.filter.equals(filter) && other.widgetId == widgetId
816 && other.userId == userId;
817 }
818
819 @Override
820 public int hashCode() {
821 return (filter == null ? 0 : filter.hashCode()) ^ (widgetId << 2) ^ (userId << 10);
822 }
823 }
824
Winson Chung499cb9f2010-07-16 11:18:17 -0700825 public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
826 mContext = context;
827 mIntent = intent;
Winson Chung81f39eb2011-01-11 18:05:01 -0800828 mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
Winson Chunga5f6f802010-09-29 09:24:21 -0700829 mLayoutInflater = LayoutInflater.from(context);
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700830 if (mIntent == null) {
831 throw new IllegalArgumentException("Non-null Intent must be specified.");
832 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700833 mRequestedViews = new RemoteViewsFrameLayoutRefSet();
Winson Chung499cb9f2010-07-16 11:18:17 -0700834
Jim Miller3e510532013-03-05 16:24:45 -0800835 checkInteractAcrossUsersPermission(context, UserHandle.myUserId());
836 mUserId = context.getUserId();
837
Winson Chung81f39eb2011-01-11 18:05:01 -0800838 // Strip the previously injected app widget id from service intent
839 if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
840 intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
841 }
842
843 // Initialize the worker thread
Winson Chung499cb9f2010-07-16 11:18:17 -0700844 mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
845 mWorkerThread.start();
846 mWorkerQueue = new Handler(mWorkerThread.getLooper());
Winson Chung81f39eb2011-01-11 18:05:01 -0800847 mMainQueue = new Handler(Looper.myLooper(), this);
Winson Chung499cb9f2010-07-16 11:18:17 -0700848
Adam Cohen335c3b62012-07-24 17:18:16 -0700849 if (sCacheRemovalThread == null) {
850 sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
851 sCacheRemovalThread.start();
852 sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
853 }
854
Winson Chung81f39eb2011-01-11 18:05:01 -0800855 // Initialize the cache and the service connection on startup
Winson Chung3ec9a452010-09-23 16:40:28 -0700856 mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
857 mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
Adam Cohen335c3b62012-07-24 17:18:16 -0700858
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800859 RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
860 mAppWidgetId, mUserId);
Adam Cohen335c3b62012-07-24 17:18:16 -0700861
862 synchronized(sCachedRemoteViewsCaches) {
863 if (sCachedRemoteViewsCaches.containsKey(key)) {
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700864 mCache = sCachedRemoteViewsCaches.get(key);
865 synchronized (mCache.mMetaData) {
866 if (mCache.mMetaData.count > 0) {
867 // As a precautionary measure, we verify that the meta data indicates a
868 // non-zero count before declaring that data is ready.
869 mDataReady = true;
870 }
871 }
Adam Cohen335c3b62012-07-24 17:18:16 -0700872 } else {
873 mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700874 }
875 if (!mDataReady) {
Adam Cohen335c3b62012-07-24 17:18:16 -0700876 requestBindService();
877 }
878 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700879 }
880
Jim Miller3e510532013-03-05 16:24:45 -0800881 private static void checkInteractAcrossUsersPermission(Context context, int userId) {
882 if (context.getUserId() != userId
883 && context.checkCallingOrSelfPermission(MULTI_USER_PERM)
884 != PackageManager.PERMISSION_GRANTED) {
885 throw new SecurityException("Must have permission " + MULTI_USER_PERM
886 + " to inflate another user's widget");
887 }
888 }
889
Jeff Brownfc442bd2011-06-10 21:34:48 -0700890 @Override
891 protected void finalize() throws Throwable {
892 try {
893 if (mWorkerThread != null) {
894 mWorkerThread.quit();
895 }
896 } finally {
897 super.finalize();
898 }
899 }
900
Adam Cohen335c3b62012-07-24 17:18:16 -0700901 public boolean isDataReady() {
902 return mDataReady;
903 }
904
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700905 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
906 mRemoteViewsOnClickHandler = handler;
907 }
908
Adam Cohen335c3b62012-07-24 17:18:16 -0700909 public void saveRemoteViewsCache() {
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800910 final RemoteViewsCacheKey key = new RemoteViewsCacheKey(
911 new Intent.FilterComparison(mIntent), mAppWidgetId, mUserId);
Adam Cohen335c3b62012-07-24 17:18:16 -0700912
913 synchronized(sCachedRemoteViewsCaches) {
914 // If we already have a remove runnable posted for this key, remove it.
915 if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
916 sCacheRemovalQueue.removeCallbacks(sRemoteViewsCacheRemoveRunnables.get(key));
917 sRemoteViewsCacheRemoveRunnables.remove(key);
918 }
919
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700920 int metaDataCount = 0;
921 int numRemoteViewsCached = 0;
922 synchronized (mCache.mMetaData) {
923 metaDataCount = mCache.mMetaData.count;
Adam Cohen335c3b62012-07-24 17:18:16 -0700924 }
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700925 synchronized (mCache) {
926 numRemoteViewsCached = mCache.mIndexRemoteViews.size();
927 }
928 if (metaDataCount > 0 && numRemoteViewsCached > 0) {
929 sCachedRemoteViewsCaches.put(key, mCache);
930 }
931
Adam Cohen335c3b62012-07-24 17:18:16 -0700932 Runnable r = new Runnable() {
933 @Override
934 public void run() {
935 synchronized (sCachedRemoteViewsCaches) {
936 if (sCachedRemoteViewsCaches.containsKey(key)) {
937 sCachedRemoteViewsCaches.remove(key);
938 }
939 if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
940 sRemoteViewsCacheRemoveRunnables.remove(key);
941 }
942 }
943 }
944 };
945 sRemoteViewsCacheRemoveRunnables.put(key, r);
946 sCacheRemovalQueue.postDelayed(r, REMOTE_VIEWS_CACHE_DURATION);
947 }
948 }
949
Winson Chung3ec9a452010-09-23 16:40:28 -0700950 private void loadNextIndexInBackground() {
951 mWorkerQueue.post(new Runnable() {
952 @Override
953 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800954 if (mServiceConnection.isConnected()) {
955 // Get the next index to load
956 int position = -1;
957 synchronized (mCache) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800958 int[] res = mCache.getNextIndexToLoad();
959 position = res[0];
Winson Chung16c8d8a2011-01-20 16:19:33 -0800960 }
961 if (position > -1) {
962 // Load the item, and notify any existing RemoteViewsFrameLayouts
Adam Cohen591ff972012-07-24 22:46:11 -0700963 updateRemoteViews(position, true);
Winson Chung3ec9a452010-09-23 16:40:28 -0700964
Winson Chung16c8d8a2011-01-20 16:19:33 -0800965 // Queue up for the next one to load
966 loadNextIndexInBackground();
967 } else {
968 // No more items to load, so queue unbind
969 enqueueDeferredUnbindServiceMessage();
970 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700971 }
972 }
973 });
974 }
975
Winson Chung16c8d8a2011-01-20 16:19:33 -0800976 private void processException(String method, Exception e) {
977 Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -0700978
Winson Chung16c8d8a2011-01-20 16:19:33 -0800979 // If we encounter a crash when updating, we should reset the metadata & cache and trigger
980 // a notifyDataSetChanged to update the widget accordingly
981 final RemoteViewsMetaData metaData = mCache.getMetaData();
982 synchronized (metaData) {
983 metaData.reset();
984 }
985 synchronized (mCache) {
986 mCache.reset();
987 }
988 mMainQueue.post(new Runnable() {
989 @Override
990 public void run() {
991 superNotifyDataSetChanged();
Winson Chung3ec9a452010-09-23 16:40:28 -0700992 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800993 });
994 }
995
996 private void updateTemporaryMetaData() {
997 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
998
999 try {
1000 // get the properties/first view (so that we can use it to
1001 // measure our dummy views)
1002 boolean hasStableIds = factory.hasStableIds();
1003 int viewTypeCount = factory.getViewTypeCount();
1004 int count = factory.getCount();
1005 RemoteViews loadingView = factory.getLoadingView();
1006 RemoteViews firstView = null;
1007 if ((count > 0) && (loadingView == null)) {
1008 firstView = factory.getViewAt(0);
1009 }
1010 final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
1011 synchronized (tmpMetaData) {
1012 tmpMetaData.hasStableIds = hasStableIds;
1013 // We +1 because the base view type is the loading view
1014 tmpMetaData.viewTypeCount = viewTypeCount + 1;
1015 tmpMetaData.count = count;
1016 tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
1017 }
Adam Cohen2625fea2011-03-23 17:24:30 -07001018 } catch(RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001019 processException("updateMetaData", e);
Adam Cohenfa2e3ff2011-04-07 16:48:19 -07001020 } catch(RuntimeException e) {
1021 processException("updateMetaData", e);
Winson Chung3ec9a452010-09-23 16:40:28 -07001022 }
1023 }
1024
Adam Cohen591ff972012-07-24 22:46:11 -07001025 private void updateRemoteViews(final int position, boolean notifyWhenLoaded) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001026 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
Winson Chung3ec9a452010-09-23 16:40:28 -07001027
Winson Chung16c8d8a2011-01-20 16:19:33 -08001028 // Load the item information from the remote service
1029 RemoteViews remoteViews = null;
1030 long itemId = 0;
1031 try {
1032 remoteViews = factory.getViewAt(position);
Amith Yamasani94022e82012-12-04 11:05:39 -08001033 remoteViews.setUser(new UserHandle(mUserId));
Winson Chung16c8d8a2011-01-20 16:19:33 -08001034 itemId = factory.getItemId(position);
Adam Cohen2625fea2011-03-23 17:24:30 -07001035 } catch (RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001036 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -07001037
Winson Chung16c8d8a2011-01-20 16:19:33 -08001038 // Return early to prevent additional work in re-centering the view cache, and
1039 // swapping from the loading view
1040 return;
Adam Cohen2625fea2011-03-23 17:24:30 -07001041 } catch (RuntimeException e) {
1042 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
1043 return;
Winson Chung16c8d8a2011-01-20 16:19:33 -08001044 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001045
Winson Chung16c8d8a2011-01-20 16:19:33 -08001046 if (remoteViews == null) {
1047 // If a null view was returned, we break early to prevent it from getting
1048 // into our cache and causing problems later. The effect is that the child at this
1049 // position will remain as a loading view until it is updated.
1050 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
1051 "returned from RemoteViewsFactory.");
1052 return;
1053 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001054
Adam Cohena5a06872012-07-11 15:23:10 -07001055 int layoutId = remoteViews.getLayoutId();
1056 RemoteViewsMetaData metaData = mCache.getMetaData();
1057 boolean viewTypeInRange;
Adam Cohen591ff972012-07-24 22:46:11 -07001058 int cacheCount;
Adam Cohena5a06872012-07-11 15:23:10 -07001059 synchronized (metaData) {
1060 viewTypeInRange = metaData.isViewTypeInRange(layoutId);
Adam Cohen591ff972012-07-24 22:46:11 -07001061 cacheCount = mCache.mMetaData.count;
Adam Cohena5a06872012-07-11 15:23:10 -07001062 }
1063 synchronized (mCache) {
1064 if (viewTypeInRange) {
Adam Cohen591ff972012-07-24 22:46:11 -07001065 ArrayList<Integer> visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
1066 mVisibleWindowUpperBound, cacheCount);
Adam Cohena5a06872012-07-11 15:23:10 -07001067 // Cache the RemoteViews we loaded
Adam Cohen591ff972012-07-24 22:46:11 -07001068 mCache.insert(position, remoteViews, itemId, visibleWindow);
Adam Cohena5a06872012-07-11 15:23:10 -07001069
1070 // Notify all the views that we have previously returned for this index that
1071 // there is new data for it.
1072 final RemoteViews rv = remoteViews;
1073 if (notifyWhenLoaded) {
1074 mMainQueue.post(new Runnable() {
1075 @Override
1076 public void run() {
1077 mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
1078 }
1079 });
1080 }
1081 } else {
1082 // We need to log an error here, as the the view type count specified by the
1083 // factory is less than the number of view types returned. We don't return this
1084 // view to the AdapterView, as this will cause an exception in the hosting process,
1085 // which contains the associated AdapterView.
1086 Log.e(TAG, "Error: widget's RemoteViewsFactory returns more view types than " +
1087 " indicated by getViewTypeCount() ");
Adam Cohenb9673922012-01-05 13:58:47 -08001088 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001089 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001090 }
1091
Winson Chung9b3a2cf2010-09-16 14:45:32 -07001092 public Intent getRemoteViewsServiceIntent() {
1093 return mIntent;
1094 }
1095
Winson Chung499cb9f2010-07-16 11:18:17 -07001096 public int getCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001097 final RemoteViewsMetaData metaData = mCache.getMetaData();
1098 synchronized (metaData) {
1099 return metaData.count;
1100 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001101 }
1102
1103 public Object getItem(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001104 // Disallow arbitrary object to be associated with an item for the time being
Winson Chung499cb9f2010-07-16 11:18:17 -07001105 return null;
1106 }
1107
1108 public long getItemId(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001109 synchronized (mCache) {
1110 if (mCache.containsMetaDataAt(position)) {
1111 return mCache.getMetaDataAt(position).itemId;
1112 }
1113 return 0;
1114 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001115 }
1116
1117 public int getItemViewType(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001118 int typeId = 0;
1119 synchronized (mCache) {
1120 if (mCache.containsMetaDataAt(position)) {
1121 typeId = mCache.getMetaDataAt(position).typeId;
1122 } else {
1123 return 0;
1124 }
1125 }
1126
1127 final RemoteViewsMetaData metaData = mCache.getMetaData();
1128 synchronized (metaData) {
1129 return metaData.getMappedViewType(typeId);
1130 }
1131 }
1132
1133 /**
1134 * Returns the item type id for the specified convert view. Returns -1 if the convert view
1135 * is invalid.
1136 */
1137 private int getConvertViewTypeId(View convertView) {
1138 int typeId = -1;
Adam Cohena32edd42010-10-26 10:35:01 -07001139 if (convertView != null) {
1140 Object tag = convertView.getTag(com.android.internal.R.id.rowTypeId);
1141 if (tag != null) {
1142 typeId = (Integer) tag;
1143 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001144 }
1145 return typeId;
Winson Chung499cb9f2010-07-16 11:18:17 -07001146 }
1147
Adam Cohenb9673922012-01-05 13:58:47 -08001148 /**
1149 * This method allows an AdapterView using this Adapter to provide information about which
1150 * views are currently being displayed. This allows for certain optimizations and preloading
1151 * which wouldn't otherwise be possible.
1152 */
1153 public void setVisibleRangeHint(int lowerBound, int upperBound) {
1154 mVisibleWindowLowerBound = lowerBound;
1155 mVisibleWindowUpperBound = upperBound;
1156 }
1157
Winson Chung499cb9f2010-07-16 11:18:17 -07001158 public View getView(int position, View convertView, ViewGroup parent) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001159 // "Request" an index so that we can queue it for loading, initiate subsequent
1160 // preloading, etc.
1161 synchronized (mCache) {
1162 boolean isInCache = mCache.containsRemoteViewAt(position);
1163 boolean isConnected = mServiceConnection.isConnected();
1164 boolean hasNewItems = false;
1165
Adam Cohenff067192012-12-05 18:14:39 -08001166 if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
1167 mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
1168 }
1169
Winson Chung7ab73e72011-03-04 15:47:23 -08001170 if (!isInCache && !isConnected) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001171 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
1172 // in turn trigger another request to getView()
1173 requestBindService();
1174 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -07001175 // Queue up other indices to be preloaded based on this position
Winson Chung16c8d8a2011-01-20 16:19:33 -08001176 hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
1177 }
1178
1179 if (isInCache) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001180 View convertViewChild = null;
1181 int convertViewTypeId = 0;
Adam Cohen181d2e32011-01-17 12:40:29 -08001182 RemoteViewsFrameLayout layout = null;
1183
1184 if (convertView instanceof RemoteViewsFrameLayout) {
1185 layout = (RemoteViewsFrameLayout) convertView;
Winson Chung3ec9a452010-09-23 16:40:28 -07001186 convertViewChild = layout.getChildAt(0);
1187 convertViewTypeId = getConvertViewTypeId(convertViewChild);
1188 }
1189
1190 // Second, we try and retrieve the RemoteViews from the cache, returning a loading
1191 // view and queueing it to be loaded if it has not already been loaded.
Winson Chung16c8d8a2011-01-20 16:19:33 -08001192 Context context = parent.getContext();
1193 RemoteViews rv = mCache.getRemoteViewsAt(position);
Adam Cohenaeb66ca2011-02-10 16:08:12 -08001194 RemoteViewsIndexMetaData indexMetaData = mCache.getMetaDataAt(position);
Adam Cohenaeb66ca2011-02-10 16:08:12 -08001195 int typeId = indexMetaData.typeId;
Winson Chung3ec9a452010-09-23 16:40:28 -07001196
Adam Cohenb7ffea62011-07-14 14:45:07 -07001197 try {
1198 // Reuse the convert view where possible
1199 if (layout != null) {
1200 if (convertViewTypeId == typeId) {
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001201 rv.reapply(context, convertViewChild, mRemoteViewsOnClickHandler);
Adam Cohenb7ffea62011-07-14 14:45:07 -07001202 return layout;
1203 }
1204 layout.removeAllViews();
1205 } else {
1206 layout = new RemoteViewsFrameLayout(context);
Winson Chung3ec9a452010-09-23 16:40:28 -07001207 }
Adam Cohenb7ffea62011-07-14 14:45:07 -07001208
1209 // Otherwise, create a new view to be returned
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001210 View newView = rv.apply(context, parent, mRemoteViewsOnClickHandler);
Adam Cohenb7ffea62011-07-14 14:45:07 -07001211 newView.setTagInternal(com.android.internal.R.id.rowTypeId,
1212 new Integer(typeId));
1213 layout.addView(newView);
1214 return layout;
1215
1216 } catch (Exception e){
1217 // We have to make sure that we successfully inflated the RemoteViews, if not
1218 // we return the loading view instead.
1219 Log.w(TAG, "Error inflating RemoteViews at position: " + position + ", using" +
1220 "loading view instead" + e);
1221
1222 RemoteViewsFrameLayout loadingView = null;
1223 final RemoteViewsMetaData metaData = mCache.getMetaData();
1224 synchronized (metaData) {
Adam Cohena5a06872012-07-11 15:23:10 -07001225 loadingView = metaData.createLoadingView(position, convertView, parent,
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001226 mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
Adam Cohenb7ffea62011-07-14 14:45:07 -07001227 }
1228 return loadingView;
1229 } finally {
1230 if (hasNewItems) loadNextIndexInBackground();
Winson Chung3ec9a452010-09-23 16:40:28 -07001231 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08001232 } else {
1233 // If the cache does not have the RemoteViews at this position, then create a
1234 // loading view and queue the actual position to be loaded in the background
1235 RemoteViewsFrameLayout loadingView = null;
1236 final RemoteViewsMetaData metaData = mCache.getMetaData();
1237 synchronized (metaData) {
Adam Cohena5a06872012-07-11 15:23:10 -07001238 loadingView = metaData.createLoadingView(position, convertView, parent,
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001239 mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
Winson Chung16c8d8a2011-01-20 16:19:33 -08001240 }
1241
1242 mRequestedViews.add(position, loadingView);
1243 mCache.queueRequestedPositionToLoad(position);
1244 loadNextIndexInBackground();
1245
1246 return loadingView;
Winson Chung3ec9a452010-09-23 16:40:28 -07001247 }
1248 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001249 }
1250
1251 public int getViewTypeCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001252 final RemoteViewsMetaData metaData = mCache.getMetaData();
1253 synchronized (metaData) {
1254 return metaData.viewTypeCount;
1255 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001256 }
1257
1258 public boolean hasStableIds() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001259 final RemoteViewsMetaData metaData = mCache.getMetaData();
1260 synchronized (metaData) {
1261 return metaData.hasStableIds;
1262 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001263 }
1264
1265 public boolean isEmpty() {
1266 return getCount() <= 0;
1267 }
1268
Winson Chung16c8d8a2011-01-20 16:19:33 -08001269 private void onNotifyDataSetChanged() {
1270 // Complete the actual notifyDataSetChanged() call initiated earlier
1271 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
1272 try {
1273 factory.onDataSetChanged();
Adam Cohen2625fea2011-03-23 17:24:30 -07001274 } catch (RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001275 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
1276
1277 // Return early to prevent from further being notified (since nothing has
1278 // changed)
1279 return;
Adam Cohen2625fea2011-03-23 17:24:30 -07001280 } catch (RuntimeException e) {
1281 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
1282 return;
Winson Chung16c8d8a2011-01-20 16:19:33 -08001283 }
1284
1285 // Flush the cache so that we can reload new items from the service
1286 synchronized (mCache) {
1287 mCache.reset();
1288 }
1289
1290 // Re-request the new metadata (only after the notification to the factory)
1291 updateTemporaryMetaData();
Adam Cohen4c994982012-04-02 13:30:32 -07001292 int newCount;
Adam Cohen591ff972012-07-24 22:46:11 -07001293 ArrayList<Integer> visibleWindow;
Adam Cohen4c994982012-04-02 13:30:32 -07001294 synchronized(mCache.getTemporaryMetaData()) {
1295 newCount = mCache.getTemporaryMetaData().count;
Adam Cohen591ff972012-07-24 22:46:11 -07001296 visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
1297 mVisibleWindowUpperBound, newCount);
Adam Cohen4c994982012-04-02 13:30:32 -07001298 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08001299
Adam Cohenb9673922012-01-05 13:58:47 -08001300 // Pre-load (our best guess of) the views which are currently visible in the AdapterView.
1301 // This mitigates flashing and flickering of loading views when a widget notifies that
1302 // its data has changed.
Adam Cohen591ff972012-07-24 22:46:11 -07001303 for (int i: visibleWindow) {
Adam Cohen4c994982012-04-02 13:30:32 -07001304 // Because temporary meta data is only ever modified from this thread (ie.
1305 // mWorkerThread), it is safe to assume that count is a valid representation.
1306 if (i < newCount) {
Adam Cohen591ff972012-07-24 22:46:11 -07001307 updateRemoteViews(i, false);
Adam Cohen4c994982012-04-02 13:30:32 -07001308 }
Adam Cohenb9673922012-01-05 13:58:47 -08001309 }
1310
Winson Chung16c8d8a2011-01-20 16:19:33 -08001311 // Propagate the notification back to the base adapter
1312 mMainQueue.post(new Runnable() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001313 @Override
1314 public void run() {
Winson Chung6364f2b2010-09-29 11:14:30 -07001315 synchronized (mCache) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001316 mCache.commitTemporaryMetaData();
Winson Chung6364f2b2010-09-29 11:14:30 -07001317 }
1318
Winson Chung16c8d8a2011-01-20 16:19:33 -08001319 superNotifyDataSetChanged();
1320 enqueueDeferredUnbindServiceMessage();
Winson Chung3ec9a452010-09-23 16:40:28 -07001321 }
1322 });
Winson Chung6364f2b2010-09-29 11:14:30 -07001323
Winson Chung16c8d8a2011-01-20 16:19:33 -08001324 // Reset the notify flagflag
1325 mNotifyDataSetChangedAfterOnServiceConnected = false;
1326 }
1327
Adam Cohen591ff972012-07-24 22:46:11 -07001328 private ArrayList<Integer> getVisibleWindow(int lower, int upper, int count) {
1329 ArrayList<Integer> window = new ArrayList<Integer>();
Adam Cohen4a9df8d2012-07-27 15:35:13 -07001330
1331 // In the case that the window is invalid or uninitialized, return an empty window.
1332 if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
1333 return window;
1334 }
1335
Adam Cohen591ff972012-07-24 22:46:11 -07001336 if (lower <= upper) {
1337 for (int i = lower; i <= upper; i++){
1338 window.add(i);
1339 }
1340 } else {
1341 // If the upper bound is less than the lower bound it means that the visible window
1342 // wraps around.
1343 for (int i = lower; i < count; i++) {
1344 window.add(i);
1345 }
1346 for (int i = 0; i <= upper; i++) {
1347 window.add(i);
1348 }
1349 }
1350 return window;
1351 }
1352
Winson Chung16c8d8a2011-01-20 16:19:33 -08001353 public void notifyDataSetChanged() {
1354 // Dequeue any unbind messages
1355 mMainQueue.removeMessages(sUnbindServiceMessageType);
1356
1357 // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
1358 // connect
1359 if (!mServiceConnection.isConnected()) {
1360 if (mNotifyDataSetChangedAfterOnServiceConnected) {
1361 return;
1362 }
1363
1364 mNotifyDataSetChangedAfterOnServiceConnected = true;
1365 requestBindService();
1366 return;
1367 }
1368
1369 mWorkerQueue.post(new Runnable() {
1370 @Override
1371 public void run() {
1372 onNotifyDataSetChanged();
1373 }
1374 });
Winson Chung3ec9a452010-09-23 16:40:28 -07001375 }
1376
Adam Cohenfb603862010-12-17 12:03:17 -08001377 void superNotifyDataSetChanged() {
Winson Chung499cb9f2010-07-16 11:18:17 -07001378 super.notifyDataSetChanged();
1379 }
1380
Winson Chung81f39eb2011-01-11 18:05:01 -08001381 @Override
1382 public boolean handleMessage(Message msg) {
1383 boolean result = false;
1384 switch (msg.what) {
1385 case sUnbindServiceMessageType:
Winson Chung81f39eb2011-01-11 18:05:01 -08001386 if (mServiceConnection.isConnected()) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001387 mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
Winson Chung81f39eb2011-01-11 18:05:01 -08001388 }
1389 result = true;
1390 break;
1391 default:
1392 break;
1393 }
1394 return result;
1395 }
1396
1397 private void enqueueDeferredUnbindServiceMessage() {
1398 // Remove any existing deferred-unbind messages
1399 mMainQueue.removeMessages(sUnbindServiceMessageType);
1400 mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
1401 }
1402
Winson Chung499cb9f2010-07-16 11:18:17 -07001403 private boolean requestBindService() {
Winson Chung81f39eb2011-01-11 18:05:01 -08001404 // Try binding the service (which will start it if it's not already running)
Winson Chung499cb9f2010-07-16 11:18:17 -07001405 if (!mServiceConnection.isConnected()) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001406 mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
Winson Chung499cb9f2010-07-16 11:18:17 -07001407 }
1408
Winson Chung16c8d8a2011-01-20 16:19:33 -08001409 // Remove any existing deferred-unbind messages
1410 mMainQueue.removeMessages(sUnbindServiceMessageType);
Winson Chung499cb9f2010-07-16 11:18:17 -07001411 return mServiceConnection.isConnected();
1412 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001413}