blob: 1c0a2bbb0f1e563c8d15d76c6fb3c76d1227a63a [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;
Winson Chung499cb9f2010-07-16 11:18:17 -070020import java.util.HashMap;
Winson Chung3ec9a452010-09-23 16:40:28 -070021import java.util.HashSet;
Winson Chungc6d6d4a2010-07-22 13:54:27 -070022import java.util.LinkedList;
Winson Chung499cb9f2010-07-16 11:18:17 -070023
Winson Chung81f39eb2011-01-11 18:05:01 -080024import android.appwidget.AppWidgetManager;
Winson Chung499cb9f2010-07-16 11:18:17 -070025import android.content.Context;
26import android.content.Intent;
Winson Chung499cb9f2010-07-16 11:18:17 -070027import android.os.Handler;
28import android.os.HandlerThread;
29import android.os.IBinder;
30import android.os.Looper;
Winson Chung81f39eb2011-01-11 18:05:01 -080031import android.os.Message;
Winson Chungfbc35902010-09-09 16:45:06 -070032import android.util.Log;
Winson Chunga5f6f802010-09-29 09:24:21 -070033import android.view.LayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070034import android.view.View;
Adam Cohen181d2e32011-01-17 12:40:29 -080035import android.view.View.MeasureSpec;
Winson Chung84bbb022011-02-21 13:57:45 -080036import android.view.ViewGroup;
Winson Chung499cb9f2010-07-16 11:18:17 -070037
Winson Chung81f39eb2011-01-11 18:05:01 -080038import com.android.internal.widget.IRemoteViewsAdapterConnection;
Winson Chung499cb9f2010-07-16 11:18:17 -070039import com.android.internal.widget.IRemoteViewsFactory;
40
41/**
42 * An adapter to a RemoteViewsService which fetches and caches RemoteViews
43 * to be later inflated as child views.
44 */
45/** @hide */
Winson Chung81f39eb2011-01-11 18:05:01 -080046public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
Winson Chungfbc35902010-09-09 16:45:06 -070047 private static final String TAG = "RemoteViewsAdapter";
Winson Chung499cb9f2010-07-16 11:18:17 -070048
Winson Chung81f39eb2011-01-11 18:05:01 -080049 // The max number of items in the cache
Winson Chungb90a91c2011-01-26 13:36:34 -080050 private static final int sDefaultCacheSize = 40;
Winson Chung81f39eb2011-01-11 18:05:01 -080051 // The delay (in millis) to wait until attempting to unbind from a service after a request.
52 // This ensures that we don't stay continually bound to the service and that it can be destroyed
53 // if we need the memory elsewhere in the system.
Winson Chungb90a91c2011-01-26 13:36:34 -080054 private static final int sUnbindServiceDelay = 5000;
Winson Chung81f39eb2011-01-11 18:05:01 -080055 // Type defs for controlling different messages across the main and worker message queues
56 private static final int sDefaultMessageType = 0;
57 private static final int sUnbindServiceMessageType = 1;
58
59 private final Context mContext;
60 private final Intent mIntent;
61 private final int mAppWidgetId;
Winson Chunga5f6f802010-09-29 09:24:21 -070062 private LayoutInflater mLayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070063 private RemoteViewsAdapterServiceConnection mServiceConnection;
Winson Chung3ec9a452010-09-23 16:40:28 -070064 private WeakReference<RemoteAdapterConnectionCallback> mCallback;
65 private FixedSizeRemoteViewsCache mCache;
66
Winson Chung16c8d8a2011-01-20 16:19:33 -080067 // A flag to determine whether we should notify data set changed after we connect
68 private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
69
Winson Chung3ec9a452010-09-23 16:40:28 -070070 // The set of requested views that are to be notified when the associated RemoteViews are
71 // loaded.
72 private RemoteViewsFrameLayoutRefSet mRequestedViews;
Winson Chung499cb9f2010-07-16 11:18:17 -070073
74 private HandlerThread mWorkerThread;
75 // items may be interrupted within the normally processed queues
76 private Handler mWorkerQueue;
77 private Handler mMainQueue;
Winson Chung499cb9f2010-07-16 11:18:17 -070078
79 /**
80 * An interface for the RemoteAdapter to notify other classes when adapters
81 * are actually connected to/disconnected from their actual services.
82 */
83 public interface RemoteAdapterConnectionCallback {
Winson Chung16c8d8a2011-01-20 16:19:33 -080084 /**
85 * @return whether the adapter was set or not.
86 */
87 public boolean onRemoteAdapterConnected();
Winson Chung499cb9f2010-07-16 11:18:17 -070088
89 public void onRemoteAdapterDisconnected();
90 }
91
92 /**
93 * The service connection that gets populated when the RemoteViewsService is
Winson Chung3ec9a452010-09-23 16:40:28 -070094 * bound. This must be a static inner class to ensure that no references to the outer
95 * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
96 * garbage collected, and would cause us to leak activities due to the caching mechanism for
97 * FrameLayouts in the adapter).
Winson Chung499cb9f2010-07-16 11:18:17 -070098 */
Winson Chung81f39eb2011-01-11 18:05:01 -080099 private static class RemoteViewsAdapterServiceConnection extends
100 IRemoteViewsAdapterConnection.Stub {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800101 private boolean mIsConnected;
102 private boolean mIsConnecting;
Winson Chung3ec9a452010-09-23 16:40:28 -0700103 private WeakReference<RemoteViewsAdapter> mAdapter;
Winson Chung499cb9f2010-07-16 11:18:17 -0700104 private IRemoteViewsFactory mRemoteViewsFactory;
Winson Chung499cb9f2010-07-16 11:18:17 -0700105
Winson Chung3ec9a452010-09-23 16:40:28 -0700106 public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
107 mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
Winson Chung499cb9f2010-07-16 11:18:17 -0700108 }
109
Winson Chung16c8d8a2011-01-20 16:19:33 -0800110 public synchronized void bind(Context context, int appWidgetId, Intent intent) {
111 if (!mIsConnecting) {
112 try {
113 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
114 mgr.bindRemoteViewsService(appWidgetId, intent, asBinder());
115 mIsConnecting = true;
116 } catch (Exception e) {
117 Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
118 mIsConnecting = false;
119 mIsConnected = false;
120 }
121 }
122 }
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700123
Winson Chung16c8d8a2011-01-20 16:19:33 -0800124 public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
125 try {
126 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
127 mgr.unbindRemoteViewsService(appWidgetId, intent);
128 mIsConnecting = false;
129 } catch (Exception e) {
130 Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
131 mIsConnecting = false;
132 mIsConnected = false;
133 }
134 }
135
136 public synchronized void onServiceConnected(IBinder service) {
137 mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
138
139 // Remove any deferred unbind messages
Winson Chung3ec9a452010-09-23 16:40:28 -0700140 final RemoteViewsAdapter adapter = mAdapter.get();
141 if (adapter == null) return;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800142
143 // Queue up work that we need to do for the callback to run
Winson Chung3ec9a452010-09-23 16:40:28 -0700144 adapter.mWorkerQueue.post(new Runnable() {
Winson Chung499cb9f2010-07-16 11:18:17 -0700145 @Override
146 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800147 if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
148 // Handle queued notifyDataSetChanged() if necessary
149 adapter.onNotifyDataSetChanged();
150 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700151 IRemoteViewsFactory factory =
152 adapter.mServiceConnection.getRemoteViewsFactory();
153 try {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800154 if (!factory.isCreated()) {
155 // We only call onDataSetChanged() if this is the factory was just
156 // create in response to this bind
157 factory.onDataSetChanged();
158 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700159 } catch (Exception e) {
160 Log.e(TAG, "Error notifying factory of data set changed in " +
161 "onServiceConnected(): " + e.getMessage());
Winson Chung499cb9f2010-07-16 11:18:17 -0700162
Winson Chung3ec9a452010-09-23 16:40:28 -0700163 // Return early to prevent anything further from being notified
164 // (effectively nothing has changed)
165 return;
Winson Chung499cb9f2010-07-16 11:18:17 -0700166 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700167
168 // Request meta data so that we have up to date data when calling back to
169 // the remote adapter callback
Winson Chung16c8d8a2011-01-20 16:19:33 -0800170 adapter.updateTemporaryMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700171
Winson Chung16c8d8a2011-01-20 16:19:33 -0800172 // Notify the host that we've connected
Winson Chung61ac7e32010-09-28 10:08:31 -0700173 adapter.mMainQueue.post(new Runnable() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700174 @Override
175 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800176 synchronized (adapter.mCache) {
177 adapter.mCache.commitTemporaryMetaData();
178 }
179
Winson Chung3ec9a452010-09-23 16:40:28 -0700180 final RemoteAdapterConnectionCallback callback =
181 adapter.mCallback.get();
182 if (callback != null) {
183 callback.onRemoteAdapterConnected();
184 }
185 }
186 });
187 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800188
189 // Enqueue unbind message
190 adapter.enqueueDeferredUnbindServiceMessage();
191 mIsConnected = true;
192 mIsConnecting = false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700193 }
194 });
195 }
196
Winson Chung16c8d8a2011-01-20 16:19:33 -0800197 public synchronized void onServiceDisconnected() {
198 mIsConnected = false;
199 mIsConnecting = false;
Winson Chung3ec9a452010-09-23 16:40:28 -0700200 mRemoteViewsFactory = null;
Winson Chung499cb9f2010-07-16 11:18:17 -0700201
Winson Chung16c8d8a2011-01-20 16:19:33 -0800202 // Clear the main/worker queues
Winson Chung3ec9a452010-09-23 16:40:28 -0700203 final RemoteViewsAdapter adapter = mAdapter.get();
204 if (adapter == null) return;
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700205
Winson Chung16c8d8a2011-01-20 16:19:33 -0800206 adapter.mMainQueue.post(new Runnable() {
207 @Override
208 public void run() {
209 // Dequeue any unbind messages
210 adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700211
Winson Chung16c8d8a2011-01-20 16:19:33 -0800212 final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
213 if (callback != null) {
214 callback.onRemoteAdapterDisconnected();
215 }
216 }
217 });
Winson Chung499cb9f2010-07-16 11:18:17 -0700218 }
219
Winson Chung16c8d8a2011-01-20 16:19:33 -0800220 public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
Winson Chung499cb9f2010-07-16 11:18:17 -0700221 return mRemoteViewsFactory;
222 }
223
Winson Chung16c8d8a2011-01-20 16:19:33 -0800224 public synchronized boolean isConnected() {
225 return mIsConnected;
Winson Chung499cb9f2010-07-16 11:18:17 -0700226 }
227 }
228
229 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700230 * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
231 * they are loaded.
Winson Chung499cb9f2010-07-16 11:18:17 -0700232 */
Winson Chung3ec9a452010-09-23 16:40:28 -0700233 private class RemoteViewsFrameLayout extends FrameLayout {
234 public RemoteViewsFrameLayout(Context context) {
235 super(context);
Winson Chung499cb9f2010-07-16 11:18:17 -0700236 }
237
238 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700239 * Updates this RemoteViewsFrameLayout depending on the view that was loaded.
240 * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
241 * successfully.
Winson Chung499cb9f2010-07-16 11:18:17 -0700242 */
Winson Chung3ec9a452010-09-23 16:40:28 -0700243 public void onRemoteViewsLoaded(RemoteViews view) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700244 try {
245 // Remove all the children of this layout first
246 removeAllViews();
247 addView(view.apply(getContext(), this));
248 } catch (Exception e) {
249 Log.e(TAG, "Failed to apply RemoteViews.");
250 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700251 }
252 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700253
Winson Chung3ec9a452010-09-23 16:40:28 -0700254 /**
255 * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
256 * adapter that have not yet had their RemoteViews loaded.
257 */
258 private class RemoteViewsFrameLayoutRefSet {
259 private HashMap<Integer, LinkedList<RemoteViewsFrameLayout>> mReferences;
260
261 public RemoteViewsFrameLayoutRefSet() {
262 mReferences = new HashMap<Integer, LinkedList<RemoteViewsFrameLayout>>();
263 }
264
265 /**
266 * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
267 */
268 public void add(int position, RemoteViewsFrameLayout layout) {
269 final Integer pos = position;
270 LinkedList<RemoteViewsFrameLayout> refs;
271
272 // Create the list if necessary
273 if (mReferences.containsKey(pos)) {
274 refs = mReferences.get(pos);
275 } else {
276 refs = new LinkedList<RemoteViewsFrameLayout>();
277 mReferences.put(pos, refs);
Winson Chung499cb9f2010-07-16 11:18:17 -0700278 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700279
280 // Add the references to the list
281 refs.add(layout);
Winson Chung499cb9f2010-07-16 11:18:17 -0700282 }
283
Winson Chung3ec9a452010-09-23 16:40:28 -0700284 /**
285 * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
286 * the associated RemoteViews has loaded.
287 */
288 public void notifyOnRemoteViewsLoaded(int position, RemoteViews view, int typeId) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700289 if (view == null) return;
290
Winson Chung3ec9a452010-09-23 16:40:28 -0700291 final Integer pos = position;
292 if (mReferences.containsKey(pos)) {
293 // Notify all the references for that position of the newly loaded RemoteViews
294 final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos);
295 for (final RemoteViewsFrameLayout ref : refs) {
296 ref.onRemoteViewsLoaded(view);
Winson Chung499cb9f2010-07-16 11:18:17 -0700297 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700298 refs.clear();
299
300 // Remove this set from the original mapping
301 mReferences.remove(pos);
Winson Chung499cb9f2010-07-16 11:18:17 -0700302 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700303 }
304
Winson Chung3ec9a452010-09-23 16:40:28 -0700305 /**
306 * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
307 */
308 public void clear() {
309 // We currently just clear the references, and leave all the previous layouts returned
310 // in their default state of the loading view.
311 mReferences.clear();
312 }
313 }
314
315 /**
316 * The meta-data associated with the cache in it's current state.
317 */
318 private class RemoteViewsMetaData {
319 int count;
320 int viewTypeCount;
321 boolean hasStableIds;
Winson Chung3ec9a452010-09-23 16:40:28 -0700322
323 // Used to determine how to construct loading views. If a loading view is not specified
324 // by the user, then we try and load the first view, and use its height as the height for
325 // the default loading view.
326 RemoteViews mUserLoadingView;
327 RemoteViews mFirstView;
328 int mFirstViewHeight;
329
330 // A mapping from type id to a set of unique type ids
Winson Chung16c8d8a2011-01-20 16:19:33 -0800331 private final HashMap<Integer, Integer> mTypeIdIndexMap = new HashMap<Integer, Integer>();
Winson Chung3ec9a452010-09-23 16:40:28 -0700332
333 public RemoteViewsMetaData() {
334 reset();
Winson Chung499cb9f2010-07-16 11:18:17 -0700335 }
336
Winson Chung16c8d8a2011-01-20 16:19:33 -0800337 public void set(RemoteViewsMetaData d) {
338 synchronized (d) {
339 count = d.count;
340 viewTypeCount = d.viewTypeCount;
341 hasStableIds = d.hasStableIds;
342 setLoadingViewTemplates(d.mUserLoadingView, d.mFirstView);
343 }
344 }
345
Winson Chung3ec9a452010-09-23 16:40:28 -0700346 public void reset() {
347 count = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800348
Winson Chung3ec9a452010-09-23 16:40:28 -0700349 // by default there is at least one dummy view type
350 viewTypeCount = 1;
351 hasStableIds = true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700352 mUserLoadingView = null;
353 mFirstView = null;
354 mFirstViewHeight = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800355 mTypeIdIndexMap.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700356 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700357
Winson Chung3ec9a452010-09-23 16:40:28 -0700358 public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
359 mUserLoadingView = loadingView;
360 if (firstView != null) {
361 mFirstView = firstView;
362 mFirstViewHeight = -1;
363 }
364 }
Winson Chungfbc35902010-09-09 16:45:06 -0700365
Winson Chung3ec9a452010-09-23 16:40:28 -0700366 public int getMappedViewType(int typeId) {
367 if (mTypeIdIndexMap.containsKey(typeId)) {
368 return mTypeIdIndexMap.get(typeId);
369 } else {
370 // We +1 because the loading view always has view type id of 0
371 int incrementalTypeId = mTypeIdIndexMap.size() + 1;
372 mTypeIdIndexMap.put(typeId, incrementalTypeId);
373 return incrementalTypeId;
374 }
375 }
376
377 private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
378 ViewGroup parent) {
379 // Create and return a new FrameLayout, and setup the references for this position
380 final Context context = parent.getContext();
381 RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
382
383 // Create a new loading view
384 synchronized (mCache) {
385 if (mUserLoadingView != null) {
386 // A user-specified loading view
387 View loadingView = mUserLoadingView.apply(parent.getContext(), parent);
Adam Cohena32edd42010-10-26 10:35:01 -0700388 loadingView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(0));
Winson Chung3ec9a452010-09-23 16:40:28 -0700389 layout.addView(loadingView);
390 } else {
391 // A default loading view
392 // Use the size of the first row as a guide for the size of the loading view
393 if (mFirstViewHeight < 0) {
394 View firstView = mFirstView.apply(parent.getContext(), parent);
395 firstView.measure(
396 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
397 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
398 mFirstViewHeight = firstView.getMeasuredHeight();
Winson Chungfbc35902010-09-09 16:45:06 -0700399 mFirstView = null;
Winson Chung499cb9f2010-07-16 11:18:17 -0700400 }
401
Winson Chung3ec9a452010-09-23 16:40:28 -0700402 // Compose the loading view text
Winson Chunga5f6f802010-09-29 09:24:21 -0700403 TextView loadingTextView = (TextView) mLayoutInflater.inflate(
Adam Cohenfb603862010-12-17 12:03:17 -0800404 com.android.internal.R.layout.remote_views_adapter_default_loading_view,
405 layout, false);
Winson Chunga5f6f802010-09-29 09:24:21 -0700406 loadingTextView.setHeight(mFirstViewHeight);
407 loadingTextView.setTag(new Integer(0));
Winson Chung3ec9a452010-09-23 16:40:28 -0700408
Winson Chunga5f6f802010-09-29 09:24:21 -0700409 layout.addView(loadingTextView);
Winson Chung499cb9f2010-07-16 11:18:17 -0700410 }
411 }
412
Winson Chung3ec9a452010-09-23 16:40:28 -0700413 return layout;
414 }
415 }
416
417 /**
418 * The meta-data associated with a single item in the cache.
419 */
420 private class RemoteViewsIndexMetaData {
421 int typeId;
422 long itemId;
Winson Chungb90a91c2011-01-26 13:36:34 -0800423 boolean isRequested;
Winson Chung3ec9a452010-09-23 16:40:28 -0700424
Winson Chungb90a91c2011-01-26 13:36:34 -0800425 public RemoteViewsIndexMetaData(RemoteViews v, long itemId, boolean requested) {
426 set(v, itemId, requested);
Winson Chung3ec9a452010-09-23 16:40:28 -0700427 }
428
Winson Chungb90a91c2011-01-26 13:36:34 -0800429 public void set(RemoteViews v, long id, boolean requested) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700430 itemId = id;
431 if (v != null)
432 typeId = v.getLayoutId();
433 else
434 typeId = 0;
Winson Chungb90a91c2011-01-26 13:36:34 -0800435 isRequested = requested;
Winson Chung3ec9a452010-09-23 16:40:28 -0700436 }
437 }
438
439 /**
440 *
441 */
442 private class FixedSizeRemoteViewsCache {
443 private static final String TAG = "FixedSizeRemoteViewsCache";
444
445 // The meta data related to all the RemoteViews, ie. count, is stable, etc.
446 private RemoteViewsMetaData mMetaData;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800447 private RemoteViewsMetaData mTemporaryMetaData;
Winson Chung3ec9a452010-09-23 16:40:28 -0700448
449 // The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
450 // greater than or equal to the set of RemoteViews.
451 // Note: The reason that we keep this separate from the RemoteViews cache below is that this
452 // we still need to be able to access the mapping of position to meta data, without keeping
453 // the heavy RemoteViews around. The RemoteViews cache is trimmed to fixed constraints wrt.
454 // memory and size, but this metadata cache will retain information until the data at the
455 // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
456 private HashMap<Integer, RemoteViewsIndexMetaData> mIndexMetaData;
457
458 // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
459 // too much memory.
460 private HashMap<Integer, RemoteViews> mIndexRemoteViews;
461
462 // The set of indices that have been explicitly requested by the collection view
463 private HashSet<Integer> mRequestedIndices;
464
Winson Chungb90a91c2011-01-26 13:36:34 -0800465 // We keep a reference of the last requested index to determine which item to prune the
466 // farthest items from when we hit the memory limit
467 private int mLastRequestedIndex;
468
Winson Chung3ec9a452010-09-23 16:40:28 -0700469 // The set of indices to load, including those explicitly requested, as well as those
470 // determined by the preloading algorithm to be prefetched
471 private HashSet<Integer> mLoadIndices;
472
473 // The lower and upper bounds of the preloaded range
474 private int mPreloadLowerBound;
475 private int mPreloadUpperBound;
476
477 // The bounds of this fixed cache, we will try and fill as many items into the cache up to
478 // the maxCount number of items, or the maxSize memory usage.
479 // The maxCountSlack is used to determine if a new position in the cache to be loaded is
480 // sufficiently ouside the old set, prompting a shifting of the "window" of items to be
481 // preloaded.
482 private int mMaxCount;
483 private int mMaxCountSlack;
484 private static final float sMaxCountSlackPercent = 0.75f;
Winson Chungb90a91c2011-01-26 13:36:34 -0800485 private static final int sMaxMemoryLimitInBytes = 2 * 1024 * 1024;
Winson Chung3ec9a452010-09-23 16:40:28 -0700486
487 public FixedSizeRemoteViewsCache(int maxCacheSize) {
488 mMaxCount = maxCacheSize;
489 mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
490 mPreloadLowerBound = 0;
491 mPreloadUpperBound = -1;
492 mMetaData = new RemoteViewsMetaData();
Winson Chung16c8d8a2011-01-20 16:19:33 -0800493 mTemporaryMetaData = new RemoteViewsMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700494 mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
495 mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
496 mRequestedIndices = new HashSet<Integer>();
Winson Chungb90a91c2011-01-26 13:36:34 -0800497 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700498 mLoadIndices = new HashSet<Integer>();
499 }
500
Winson Chungb90a91c2011-01-26 13:36:34 -0800501 public void insert(int position, RemoteViews v, long itemId, boolean isRequested) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700502 // Trim the cache if we go beyond the count
503 if (mIndexRemoteViews.size() >= mMaxCount) {
504 mIndexRemoteViews.remove(getFarthestPositionFrom(position));
Winson Chung499cb9f2010-07-16 11:18:17 -0700505 }
506
Winson Chung3ec9a452010-09-23 16:40:28 -0700507 // Trim the cache if we go beyond the available memory size constraints
Winson Chungb90a91c2011-01-26 13:36:34 -0800508 int pruneFromPosition = (mLastRequestedIndex > -1) ? mLastRequestedIndex : position;
509 while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryLimitInBytes) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700510 // Note: This is currently the most naive mechanism for deciding what to prune when
511 // we hit the memory limit. In the future, we may want to calculate which index to
512 // remove based on both its position as well as it's current memory usage, as well
513 // as whether it was directly requested vs. whether it was preloaded by our caching
514 // mechanism.
Winson Chungb90a91c2011-01-26 13:36:34 -0800515 mIndexRemoteViews.remove(getFarthestPositionFrom(pruneFromPosition));
Winson Chung3ec9a452010-09-23 16:40:28 -0700516 }
517
518 // Update the metadata cache
519 if (mIndexMetaData.containsKey(position)) {
520 final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
Winson Chungb90a91c2011-01-26 13:36:34 -0800521 metaData.set(v, itemId, isRequested);
Winson Chung3ec9a452010-09-23 16:40:28 -0700522 } else {
Winson Chungb90a91c2011-01-26 13:36:34 -0800523 mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId, isRequested));
Winson Chung3ec9a452010-09-23 16:40:28 -0700524 }
525 mIndexRemoteViews.put(position, v);
526 }
527
528 public RemoteViewsMetaData getMetaData() {
529 return mMetaData;
530 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800531 public RemoteViewsMetaData getTemporaryMetaData() {
532 return mTemporaryMetaData;
533 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700534 public RemoteViews getRemoteViewsAt(int position) {
535 if (mIndexRemoteViews.containsKey(position)) {
536 return mIndexRemoteViews.get(position);
537 }
538 return null;
539 }
540 public RemoteViewsIndexMetaData getMetaDataAt(int position) {
541 if (mIndexMetaData.containsKey(position)) {
542 return mIndexMetaData.get(position);
543 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700544 return null;
545 }
546
Winson Chung16c8d8a2011-01-20 16:19:33 -0800547 public void commitTemporaryMetaData() {
548 synchronized (mTemporaryMetaData) {
549 synchronized (mMetaData) {
550 mMetaData.set(mTemporaryMetaData);
551 }
552 }
553 }
554
Winson Chung3ec9a452010-09-23 16:40:28 -0700555 private int getRemoteViewsBitmapMemoryUsage() {
556 // Calculate the memory usage of all the RemoteViews bitmaps being cached
557 int mem = 0;
558 for (Integer i : mIndexRemoteViews.keySet()) {
559 final RemoteViews v = mIndexRemoteViews.get(i);
Winson Chungaaffa8b2010-10-30 14:04:05 -0700560 if (v != null) {
561 mem += v.estimateBitmapMemoryUsage();
562 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700563 }
564 return mem;
565 }
566 private int getFarthestPositionFrom(int pos) {
567 // Find the index farthest away and remove that
568 int maxDist = 0;
569 int maxDistIndex = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800570 int maxDistNonRequested = 0;
571 int maxDistIndexNonRequested = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700572 for (int i : mIndexRemoteViews.keySet()) {
573 int dist = Math.abs(i-pos);
Winson Chungb90a91c2011-01-26 13:36:34 -0800574 if (dist > maxDistNonRequested && !mIndexMetaData.get(i).isRequested) {
575 // maxDistNonRequested/maxDistIndexNonRequested will store the index of the
576 // farthest non-requested position
577 maxDistIndexNonRequested = i;
578 maxDistNonRequested = dist;
579 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700580 if (dist > maxDist) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800581 // maxDist/maxDistIndex will store the index of the farthest position
582 // regardless of whether it was directly requested or not
Winson Chung3ec9a452010-09-23 16:40:28 -0700583 maxDistIndex = i;
584 maxDist = dist;
Winson Chung499cb9f2010-07-16 11:18:17 -0700585 }
586 }
Winson Chungb90a91c2011-01-26 13:36:34 -0800587 if (maxDistIndexNonRequested > -1) {
588 return maxDistIndexNonRequested;
589 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700590 return maxDistIndex;
Winson Chung499cb9f2010-07-16 11:18:17 -0700591 }
592
Winson Chung3ec9a452010-09-23 16:40:28 -0700593 public void queueRequestedPositionToLoad(int position) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800594 mLastRequestedIndex = position;
Winson Chung3ec9a452010-09-23 16:40:28 -0700595 synchronized (mLoadIndices) {
596 mRequestedIndices.add(position);
597 mLoadIndices.add(position);
598 }
599 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800600 public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700601 // Check if we need to preload any items
602 if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
603 int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
604 if (Math.abs(position - center) < mMaxCountSlack) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800605 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700606 }
607 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700608
Winson Chung3ec9a452010-09-23 16:40:28 -0700609 int count = 0;
610 synchronized (mMetaData) {
611 count = mMetaData.count;
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700612 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700613 synchronized (mLoadIndices) {
614 mLoadIndices.clear();
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700615
Winson Chung3ec9a452010-09-23 16:40:28 -0700616 // Add all the requested indices
617 mLoadIndices.addAll(mRequestedIndices);
Winson Chung499cb9f2010-07-16 11:18:17 -0700618
Winson Chung3ec9a452010-09-23 16:40:28 -0700619 // Add all the preload indices
620 int halfMaxCount = mMaxCount / 2;
621 mPreloadLowerBound = position - halfMaxCount;
622 mPreloadUpperBound = position + halfMaxCount;
623 int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
624 int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
625 for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
626 mLoadIndices.add(i);
Winson Chung499cb9f2010-07-16 11:18:17 -0700627 }
628
Winson Chung3ec9a452010-09-23 16:40:28 -0700629 // But remove all the indices that have already been loaded and are cached
630 mLoadIndices.removeAll(mIndexRemoteViews.keySet());
631 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800632 return true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700633 }
Winson Chungb90a91c2011-01-26 13:36:34 -0800634 /** Returns the next index to load, and whether that index was directly requested or not */
635 public int[] getNextIndexToLoad() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700636 // We try and prioritize items that have been requested directly, instead
637 // of items that are loaded as a result of the caching mechanism
638 synchronized (mLoadIndices) {
639 // Prioritize requested indices to be loaded first
640 if (!mRequestedIndices.isEmpty()) {
641 Integer i = mRequestedIndices.iterator().next();
642 mRequestedIndices.remove(i);
643 mLoadIndices.remove(i);
Winson Chungb90a91c2011-01-26 13:36:34 -0800644 return new int[]{i.intValue(), 1};
Winson Chung3ec9a452010-09-23 16:40:28 -0700645 }
646
647 // Otherwise, preload other indices as necessary
648 if (!mLoadIndices.isEmpty()) {
649 Integer i = mLoadIndices.iterator().next();
650 mLoadIndices.remove(i);
Winson Chungb90a91c2011-01-26 13:36:34 -0800651 return new int[]{i.intValue(), 0};
Winson Chung3ec9a452010-09-23 16:40:28 -0700652 }
653
Winson Chungb90a91c2011-01-26 13:36:34 -0800654 return new int[]{-1, 0};
Winson Chung3ec9a452010-09-23 16:40:28 -0700655 }
656 }
657
658 public boolean containsRemoteViewAt(int position) {
659 return mIndexRemoteViews.containsKey(position);
660 }
661 public boolean containsMetaDataAt(int position) {
662 return mIndexMetaData.containsKey(position);
663 }
664
665 public void reset() {
Winson Chung61ac7e32010-09-28 10:08:31 -0700666 // Note: We do not try and reset the meta data, since that information is still used by
667 // collection views to validate it's own contents (and will be re-requested if the data
668 // is invalidated through the notifyDataSetChanged() flow).
669
Winson Chung3ec9a452010-09-23 16:40:28 -0700670 mPreloadLowerBound = 0;
671 mPreloadUpperBound = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800672 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700673 mIndexRemoteViews.clear();
674 mIndexMetaData.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700675 synchronized (mLoadIndices) {
676 mRequestedIndices.clear();
677 mLoadIndices.clear();
Winson Chung499cb9f2010-07-16 11:18:17 -0700678 }
679 }
680 }
681
682 public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
683 mContext = context;
684 mIntent = intent;
Winson Chung81f39eb2011-01-11 18:05:01 -0800685 mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
Winson Chunga5f6f802010-09-29 09:24:21 -0700686 mLayoutInflater = LayoutInflater.from(context);
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700687 if (mIntent == null) {
688 throw new IllegalArgumentException("Non-null Intent must be specified.");
689 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700690 mRequestedViews = new RemoteViewsFrameLayoutRefSet();
Winson Chung499cb9f2010-07-16 11:18:17 -0700691
Winson Chung81f39eb2011-01-11 18:05:01 -0800692 // Strip the previously injected app widget id from service intent
693 if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
694 intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
695 }
696
697 // Initialize the worker thread
Winson Chung499cb9f2010-07-16 11:18:17 -0700698 mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
699 mWorkerThread.start();
700 mWorkerQueue = new Handler(mWorkerThread.getLooper());
Winson Chung81f39eb2011-01-11 18:05:01 -0800701 mMainQueue = new Handler(Looper.myLooper(), this);
Winson Chung499cb9f2010-07-16 11:18:17 -0700702
Winson Chung81f39eb2011-01-11 18:05:01 -0800703 // Initialize the cache and the service connection on startup
704 mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
Winson Chung3ec9a452010-09-23 16:40:28 -0700705 mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
706 mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
Winson Chung499cb9f2010-07-16 11:18:17 -0700707 requestBindService();
708 }
709
Winson Chung3ec9a452010-09-23 16:40:28 -0700710 private void loadNextIndexInBackground() {
711 mWorkerQueue.post(new Runnable() {
712 @Override
713 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800714 if (mServiceConnection.isConnected()) {
715 // Get the next index to load
716 int position = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800717 boolean isRequested = false;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800718 synchronized (mCache) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800719 int[] res = mCache.getNextIndexToLoad();
720 position = res[0];
721 isRequested = res[1] > 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800722 }
723 if (position > -1) {
724 // Load the item, and notify any existing RemoteViewsFrameLayouts
Winson Chungb90a91c2011-01-26 13:36:34 -0800725 updateRemoteViews(position, isRequested);
Winson Chung3ec9a452010-09-23 16:40:28 -0700726
Winson Chung16c8d8a2011-01-20 16:19:33 -0800727 // Queue up for the next one to load
728 loadNextIndexInBackground();
729 } else {
730 // No more items to load, so queue unbind
731 enqueueDeferredUnbindServiceMessage();
732 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700733 }
734 }
735 });
736 }
737
Winson Chung16c8d8a2011-01-20 16:19:33 -0800738 private void processException(String method, Exception e) {
739 Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -0700740
Winson Chung16c8d8a2011-01-20 16:19:33 -0800741 // If we encounter a crash when updating, we should reset the metadata & cache and trigger
742 // a notifyDataSetChanged to update the widget accordingly
743 final RemoteViewsMetaData metaData = mCache.getMetaData();
744 synchronized (metaData) {
745 metaData.reset();
746 }
747 synchronized (mCache) {
748 mCache.reset();
749 }
750 mMainQueue.post(new Runnable() {
751 @Override
752 public void run() {
753 superNotifyDataSetChanged();
Winson Chung3ec9a452010-09-23 16:40:28 -0700754 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800755 });
756 }
757
758 private void updateTemporaryMetaData() {
759 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
760
761 try {
762 // get the properties/first view (so that we can use it to
763 // measure our dummy views)
764 boolean hasStableIds = factory.hasStableIds();
765 int viewTypeCount = factory.getViewTypeCount();
766 int count = factory.getCount();
767 RemoteViews loadingView = factory.getLoadingView();
768 RemoteViews firstView = null;
769 if ((count > 0) && (loadingView == null)) {
770 firstView = factory.getViewAt(0);
771 }
772 final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
773 synchronized (tmpMetaData) {
774 tmpMetaData.hasStableIds = hasStableIds;
775 // We +1 because the base view type is the loading view
776 tmpMetaData.viewTypeCount = viewTypeCount + 1;
777 tmpMetaData.count = count;
778 tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
779 }
780 } catch (Exception e) {
781 processException("updateMetaData", e);
Winson Chung3ec9a452010-09-23 16:40:28 -0700782 }
783 }
784
Winson Chungb90a91c2011-01-26 13:36:34 -0800785 private void updateRemoteViews(final int position, boolean isRequested) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800786 if (!mServiceConnection.isConnected()) return;
787 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
Winson Chung3ec9a452010-09-23 16:40:28 -0700788
Winson Chung16c8d8a2011-01-20 16:19:33 -0800789 // Load the item information from the remote service
790 RemoteViews remoteViews = null;
791 long itemId = 0;
792 try {
793 remoteViews = factory.getViewAt(position);
794 itemId = factory.getItemId(position);
795 } catch (Exception e) {
796 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -0700797
Winson Chung16c8d8a2011-01-20 16:19:33 -0800798 // Return early to prevent additional work in re-centering the view cache, and
799 // swapping from the loading view
800 return;
801 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700802
Winson Chung16c8d8a2011-01-20 16:19:33 -0800803 if (remoteViews == null) {
804 // If a null view was returned, we break early to prevent it from getting
805 // into our cache and causing problems later. The effect is that the child at this
806 // position will remain as a loading view until it is updated.
807 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
808 "returned from RemoteViewsFactory.");
809 return;
810 }
811 synchronized (mCache) {
812 // Cache the RemoteViews we loaded
Winson Chungb90a91c2011-01-26 13:36:34 -0800813 mCache.insert(position, remoteViews, itemId, isRequested);
Winson Chung3ec9a452010-09-23 16:40:28 -0700814
Winson Chung16c8d8a2011-01-20 16:19:33 -0800815 // Notify all the views that we have previously returned for this index that
816 // there is new data for it.
817 final RemoteViews rv = remoteViews;
818 final int typeId = mCache.getMetaDataAt(position).typeId;
819 mMainQueue.post(new Runnable() {
820 @Override
821 public void run() {
822 mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
823 }
824 });
Winson Chung3ec9a452010-09-23 16:40:28 -0700825 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700826 }
827
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700828 public Intent getRemoteViewsServiceIntent() {
829 return mIntent;
830 }
831
Winson Chung499cb9f2010-07-16 11:18:17 -0700832 public int getCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700833 final RemoteViewsMetaData metaData = mCache.getMetaData();
834 synchronized (metaData) {
835 return metaData.count;
836 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700837 }
838
839 public Object getItem(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700840 // Disallow arbitrary object to be associated with an item for the time being
Winson Chung499cb9f2010-07-16 11:18:17 -0700841 return null;
842 }
843
844 public long getItemId(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700845 synchronized (mCache) {
846 if (mCache.containsMetaDataAt(position)) {
847 return mCache.getMetaDataAt(position).itemId;
848 }
849 return 0;
850 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700851 }
852
853 public int getItemViewType(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700854 int typeId = 0;
855 synchronized (mCache) {
856 if (mCache.containsMetaDataAt(position)) {
857 typeId = mCache.getMetaDataAt(position).typeId;
858 } else {
859 return 0;
860 }
861 }
862
863 final RemoteViewsMetaData metaData = mCache.getMetaData();
864 synchronized (metaData) {
865 return metaData.getMappedViewType(typeId);
866 }
867 }
868
869 /**
870 * Returns the item type id for the specified convert view. Returns -1 if the convert view
871 * is invalid.
872 */
873 private int getConvertViewTypeId(View convertView) {
874 int typeId = -1;
Adam Cohena32edd42010-10-26 10:35:01 -0700875 if (convertView != null) {
876 Object tag = convertView.getTag(com.android.internal.R.id.rowTypeId);
877 if (tag != null) {
878 typeId = (Integer) tag;
879 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700880 }
881 return typeId;
Winson Chung499cb9f2010-07-16 11:18:17 -0700882 }
883
884 public View getView(int position, View convertView, ViewGroup parent) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800885 // "Request" an index so that we can queue it for loading, initiate subsequent
886 // preloading, etc.
887 synchronized (mCache) {
888 boolean isInCache = mCache.containsRemoteViewAt(position);
889 boolean isConnected = mServiceConnection.isConnected();
890 boolean hasNewItems = false;
891
Winson Chung7ab73e72011-03-04 15:47:23 -0800892 if (!isInCache && !isConnected) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800893 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
894 // in turn trigger another request to getView()
895 requestBindService();
896 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700897 // Queue up other indices to be preloaded based on this position
Winson Chung16c8d8a2011-01-20 16:19:33 -0800898 hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
899 }
900
901 if (isInCache) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700902 View convertViewChild = null;
903 int convertViewTypeId = 0;
Adam Cohen181d2e32011-01-17 12:40:29 -0800904 RemoteViewsFrameLayout layout = null;
905
906 if (convertView instanceof RemoteViewsFrameLayout) {
907 layout = (RemoteViewsFrameLayout) convertView;
Winson Chung3ec9a452010-09-23 16:40:28 -0700908 convertViewChild = layout.getChildAt(0);
909 convertViewTypeId = getConvertViewTypeId(convertViewChild);
910 }
911
912 // Second, we try and retrieve the RemoteViews from the cache, returning a loading
913 // view and queueing it to be loaded if it has not already been loaded.
Winson Chung16c8d8a2011-01-20 16:19:33 -0800914 Context context = parent.getContext();
915 RemoteViews rv = mCache.getRemoteViewsAt(position);
Adam Cohenaeb66ca2011-02-10 16:08:12 -0800916 RemoteViewsIndexMetaData indexMetaData = mCache.getMetaDataAt(position);
917 indexMetaData.isRequested = true;
918 int typeId = indexMetaData.typeId;
Winson Chung3ec9a452010-09-23 16:40:28 -0700919
Winson Chung16c8d8a2011-01-20 16:19:33 -0800920 // Reuse the convert view where possible
921 if (layout != null) {
922 if (convertViewTypeId == typeId) {
923 rv.reapply(context, convertViewChild);
924 return layout;
Winson Chung3ec9a452010-09-23 16:40:28 -0700925 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800926 layout.removeAllViews();
Winson Chung3ec9a452010-09-23 16:40:28 -0700927 } else {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800928 layout = new RemoteViewsFrameLayout(context);
Winson Chung3ec9a452010-09-23 16:40:28 -0700929 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800930
931 // Otherwise, create a new view to be returned
932 View newView = rv.apply(context, parent);
933 newView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(typeId));
934 layout.addView(newView);
935 if (hasNewItems) loadNextIndexInBackground();
936
937 return layout;
938 } else {
939 // If the cache does not have the RemoteViews at this position, then create a
940 // loading view and queue the actual position to be loaded in the background
941 RemoteViewsFrameLayout loadingView = null;
942 final RemoteViewsMetaData metaData = mCache.getMetaData();
943 synchronized (metaData) {
944 loadingView = metaData.createLoadingView(position, convertView, parent);
945 }
946
947 mRequestedViews.add(position, loadingView);
948 mCache.queueRequestedPositionToLoad(position);
949 loadNextIndexInBackground();
950
951 return loadingView;
Winson Chung3ec9a452010-09-23 16:40:28 -0700952 }
953 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700954 }
955
956 public int getViewTypeCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700957 final RemoteViewsMetaData metaData = mCache.getMetaData();
958 synchronized (metaData) {
959 return metaData.viewTypeCount;
960 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700961 }
962
963 public boolean hasStableIds() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700964 final RemoteViewsMetaData metaData = mCache.getMetaData();
965 synchronized (metaData) {
966 return metaData.hasStableIds;
967 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700968 }
969
970 public boolean isEmpty() {
971 return getCount() <= 0;
972 }
973
Winson Chung16c8d8a2011-01-20 16:19:33 -0800974
975 private void onNotifyDataSetChanged() {
976 // Complete the actual notifyDataSetChanged() call initiated earlier
977 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
978 try {
979 factory.onDataSetChanged();
980 } catch (Exception e) {
981 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
982
983 // Return early to prevent from further being notified (since nothing has
984 // changed)
985 return;
986 }
987
988 // Flush the cache so that we can reload new items from the service
989 synchronized (mCache) {
990 mCache.reset();
991 }
992
993 // Re-request the new metadata (only after the notification to the factory)
994 updateTemporaryMetaData();
995
996 // Propagate the notification back to the base adapter
997 mMainQueue.post(new Runnable() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700998 @Override
999 public void run() {
Winson Chung6364f2b2010-09-29 11:14:30 -07001000 synchronized (mCache) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001001 mCache.commitTemporaryMetaData();
Winson Chung6364f2b2010-09-29 11:14:30 -07001002 }
1003
Winson Chung16c8d8a2011-01-20 16:19:33 -08001004 superNotifyDataSetChanged();
1005 enqueueDeferredUnbindServiceMessage();
Winson Chung3ec9a452010-09-23 16:40:28 -07001006 }
1007 });
Winson Chung6364f2b2010-09-29 11:14:30 -07001008
Winson Chung16c8d8a2011-01-20 16:19:33 -08001009 // Reset the notify flagflag
1010 mNotifyDataSetChangedAfterOnServiceConnected = false;
1011 }
1012
1013 public void notifyDataSetChanged() {
1014 // Dequeue any unbind messages
1015 mMainQueue.removeMessages(sUnbindServiceMessageType);
1016
1017 // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
1018 // connect
1019 if (!mServiceConnection.isConnected()) {
1020 if (mNotifyDataSetChangedAfterOnServiceConnected) {
1021 return;
1022 }
1023
1024 mNotifyDataSetChangedAfterOnServiceConnected = true;
1025 requestBindService();
1026 return;
1027 }
1028
1029 mWorkerQueue.post(new Runnable() {
1030 @Override
1031 public void run() {
1032 onNotifyDataSetChanged();
1033 }
1034 });
Winson Chung3ec9a452010-09-23 16:40:28 -07001035 }
1036
Adam Cohenfb603862010-12-17 12:03:17 -08001037 void superNotifyDataSetChanged() {
Winson Chung499cb9f2010-07-16 11:18:17 -07001038 super.notifyDataSetChanged();
1039 }
1040
Winson Chung81f39eb2011-01-11 18:05:01 -08001041 @Override
1042 public boolean handleMessage(Message msg) {
1043 boolean result = false;
1044 switch (msg.what) {
1045 case sUnbindServiceMessageType:
Winson Chung81f39eb2011-01-11 18:05:01 -08001046 if (mServiceConnection.isConnected()) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001047 mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
Winson Chung81f39eb2011-01-11 18:05:01 -08001048 }
1049 result = true;
1050 break;
1051 default:
1052 break;
1053 }
1054 return result;
1055 }
1056
1057 private void enqueueDeferredUnbindServiceMessage() {
1058 // Remove any existing deferred-unbind messages
1059 mMainQueue.removeMessages(sUnbindServiceMessageType);
1060 mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
1061 }
1062
Winson Chung499cb9f2010-07-16 11:18:17 -07001063 private boolean requestBindService() {
Winson Chung81f39eb2011-01-11 18:05:01 -08001064 // Try binding the service (which will start it if it's not already running)
Winson Chung499cb9f2010-07-16 11:18:17 -07001065 if (!mServiceConnection.isConnected()) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001066 mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
Winson Chung499cb9f2010-07-16 11:18:17 -07001067 }
1068
Winson Chung16c8d8a2011-01-20 16:19:33 -08001069 // Remove any existing deferred-unbind messages
1070 mMainQueue.removeMessages(sUnbindServiceMessageType);
Winson Chung499cb9f2010-07-16 11:18:17 -07001071 return mServiceConnection.isConnected();
1072 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001073}