blob: 40b0a9c7b169469cf7ac27aa0c088243b07fdca4 [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;
Adam Cohen2625fea2011-03-23 17:24:30 -070032import android.os.RemoteException;
Winson Chungfbc35902010-09-09 16:45:06 -070033import android.util.Log;
Winson Chunga5f6f802010-09-29 09:24:21 -070034import android.view.LayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070035import android.view.View;
Adam Cohen181d2e32011-01-17 12:40:29 -080036import android.view.View.MeasureSpec;
Winson Chung84bbb022011-02-21 13:57:45 -080037import android.view.ViewGroup;
Winson Chung499cb9f2010-07-16 11:18:17 -070038
Winson Chung81f39eb2011-01-11 18:05:01 -080039import com.android.internal.widget.IRemoteViewsAdapterConnection;
Winson Chung499cb9f2010-07-16 11:18:17 -070040import com.android.internal.widget.IRemoteViewsFactory;
41
42/**
43 * An adapter to a RemoteViewsService which fetches and caches RemoteViews
44 * to be later inflated as child views.
45 */
46/** @hide */
Winson Chung81f39eb2011-01-11 18:05:01 -080047public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
Winson Chungfbc35902010-09-09 16:45:06 -070048 private static final String TAG = "RemoteViewsAdapter";
Winson Chung499cb9f2010-07-16 11:18:17 -070049
Winson Chung81f39eb2011-01-11 18:05:01 -080050 // The max number of items in the cache
Winson Chungb90a91c2011-01-26 13:36:34 -080051 private static final int sDefaultCacheSize = 40;
Winson Chung81f39eb2011-01-11 18:05:01 -080052 // The delay (in millis) to wait until attempting to unbind from a service after a request.
53 // This ensures that we don't stay continually bound to the service and that it can be destroyed
54 // if we need the memory elsewhere in the system.
Winson Chungb90a91c2011-01-26 13:36:34 -080055 private static final int sUnbindServiceDelay = 5000;
Winson Chung81f39eb2011-01-11 18:05:01 -080056 // Type defs for controlling different messages across the main and worker message queues
57 private static final int sDefaultMessageType = 0;
58 private static final int sUnbindServiceMessageType = 1;
59
60 private final Context mContext;
61 private final Intent mIntent;
62 private final int mAppWidgetId;
Winson Chunga5f6f802010-09-29 09:24:21 -070063 private LayoutInflater mLayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070064 private RemoteViewsAdapterServiceConnection mServiceConnection;
Winson Chung3ec9a452010-09-23 16:40:28 -070065 private WeakReference<RemoteAdapterConnectionCallback> mCallback;
66 private FixedSizeRemoteViewsCache mCache;
67
Winson Chung16c8d8a2011-01-20 16:19:33 -080068 // A flag to determine whether we should notify data set changed after we connect
69 private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
70
Winson Chung3ec9a452010-09-23 16:40:28 -070071 // The set of requested views that are to be notified when the associated RemoteViews are
72 // loaded.
73 private RemoteViewsFrameLayoutRefSet mRequestedViews;
Winson Chung499cb9f2010-07-16 11:18:17 -070074
75 private HandlerThread mWorkerThread;
76 // items may be interrupted within the normally processed queues
77 private Handler mWorkerQueue;
78 private Handler mMainQueue;
Winson Chung499cb9f2010-07-16 11:18:17 -070079
80 /**
81 * An interface for the RemoteAdapter to notify other classes when adapters
82 * are actually connected to/disconnected from their actual services.
83 */
84 public interface RemoteAdapterConnectionCallback {
Winson Chung16c8d8a2011-01-20 16:19:33 -080085 /**
86 * @return whether the adapter was set or not.
87 */
88 public boolean onRemoteAdapterConnected();
Winson Chung499cb9f2010-07-16 11:18:17 -070089
90 public void onRemoteAdapterDisconnected();
91 }
92
93 /**
94 * The service connection that gets populated when the RemoteViewsService is
Winson Chung3ec9a452010-09-23 16:40:28 -070095 * bound. This must be a static inner class to ensure that no references to the outer
96 * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
97 * garbage collected, and would cause us to leak activities due to the caching mechanism for
98 * FrameLayouts in the adapter).
Winson Chung499cb9f2010-07-16 11:18:17 -070099 */
Winson Chung81f39eb2011-01-11 18:05:01 -0800100 private static class RemoteViewsAdapterServiceConnection extends
101 IRemoteViewsAdapterConnection.Stub {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800102 private boolean mIsConnected;
103 private boolean mIsConnecting;
Winson Chung3ec9a452010-09-23 16:40:28 -0700104 private WeakReference<RemoteViewsAdapter> mAdapter;
Winson Chung499cb9f2010-07-16 11:18:17 -0700105 private IRemoteViewsFactory mRemoteViewsFactory;
Winson Chung499cb9f2010-07-16 11:18:17 -0700106
Winson Chung3ec9a452010-09-23 16:40:28 -0700107 public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
108 mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
Winson Chung499cb9f2010-07-16 11:18:17 -0700109 }
110
Winson Chung16c8d8a2011-01-20 16:19:33 -0800111 public synchronized void bind(Context context, int appWidgetId, Intent intent) {
112 if (!mIsConnecting) {
113 try {
114 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
115 mgr.bindRemoteViewsService(appWidgetId, intent, asBinder());
116 mIsConnecting = true;
117 } catch (Exception e) {
118 Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
119 mIsConnecting = false;
120 mIsConnected = false;
121 }
122 }
123 }
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700124
Winson Chung16c8d8a2011-01-20 16:19:33 -0800125 public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
126 try {
127 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
128 mgr.unbindRemoteViewsService(appWidgetId, intent);
129 mIsConnecting = false;
130 } catch (Exception e) {
131 Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
132 mIsConnecting = false;
133 mIsConnected = false;
134 }
135 }
136
137 public synchronized void onServiceConnected(IBinder service) {
138 mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
139
140 // Remove any deferred unbind messages
Winson Chung3ec9a452010-09-23 16:40:28 -0700141 final RemoteViewsAdapter adapter = mAdapter.get();
142 if (adapter == null) return;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800143
144 // Queue up work that we need to do for the callback to run
Winson Chung3ec9a452010-09-23 16:40:28 -0700145 adapter.mWorkerQueue.post(new Runnable() {
Winson Chung499cb9f2010-07-16 11:18:17 -0700146 @Override
147 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800148 if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
149 // Handle queued notifyDataSetChanged() if necessary
150 adapter.onNotifyDataSetChanged();
151 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700152 IRemoteViewsFactory factory =
153 adapter.mServiceConnection.getRemoteViewsFactory();
154 try {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800155 if (!factory.isCreated()) {
156 // We only call onDataSetChanged() if this is the factory was just
157 // create in response to this bind
158 factory.onDataSetChanged();
159 }
Adam Cohen2625fea2011-03-23 17:24:30 -0700160 } catch (RemoteException e) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700161 Log.e(TAG, "Error notifying factory of data set changed in " +
162 "onServiceConnected(): " + e.getMessage());
Winson Chung499cb9f2010-07-16 11:18:17 -0700163
Winson Chung3ec9a452010-09-23 16:40:28 -0700164 // Return early to prevent anything further from being notified
165 // (effectively nothing has changed)
166 return;
Adam Cohen2625fea2011-03-23 17:24:30 -0700167 } catch (RuntimeException e) {
168 Log.e(TAG, "Error notifying factory of data set changed in " +
169 "onServiceConnected(): " + e.getMessage());
Winson Chung499cb9f2010-07-16 11:18:17 -0700170 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700171
172 // Request meta data so that we have up to date data when calling back to
173 // the remote adapter callback
Winson Chung16c8d8a2011-01-20 16:19:33 -0800174 adapter.updateTemporaryMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700175
Winson Chung16c8d8a2011-01-20 16:19:33 -0800176 // Notify the host that we've connected
Winson Chung61ac7e32010-09-28 10:08:31 -0700177 adapter.mMainQueue.post(new Runnable() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700178 @Override
179 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800180 synchronized (adapter.mCache) {
181 adapter.mCache.commitTemporaryMetaData();
182 }
183
Winson Chung3ec9a452010-09-23 16:40:28 -0700184 final RemoteAdapterConnectionCallback callback =
185 adapter.mCallback.get();
186 if (callback != null) {
187 callback.onRemoteAdapterConnected();
188 }
189 }
190 });
191 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800192
193 // Enqueue unbind message
194 adapter.enqueueDeferredUnbindServiceMessage();
195 mIsConnected = true;
196 mIsConnecting = false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700197 }
198 });
199 }
200
Winson Chung16c8d8a2011-01-20 16:19:33 -0800201 public synchronized void onServiceDisconnected() {
202 mIsConnected = false;
203 mIsConnecting = false;
Winson Chung3ec9a452010-09-23 16:40:28 -0700204 mRemoteViewsFactory = null;
Winson Chung499cb9f2010-07-16 11:18:17 -0700205
Winson Chung16c8d8a2011-01-20 16:19:33 -0800206 // Clear the main/worker queues
Winson Chung3ec9a452010-09-23 16:40:28 -0700207 final RemoteViewsAdapter adapter = mAdapter.get();
208 if (adapter == null) return;
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700209
Winson Chung16c8d8a2011-01-20 16:19:33 -0800210 adapter.mMainQueue.post(new Runnable() {
211 @Override
212 public void run() {
213 // Dequeue any unbind messages
214 adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700215
Winson Chung16c8d8a2011-01-20 16:19:33 -0800216 final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
217 if (callback != null) {
218 callback.onRemoteAdapterDisconnected();
219 }
220 }
221 });
Winson Chung499cb9f2010-07-16 11:18:17 -0700222 }
223
Winson Chung16c8d8a2011-01-20 16:19:33 -0800224 public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
Winson Chung499cb9f2010-07-16 11:18:17 -0700225 return mRemoteViewsFactory;
226 }
227
Winson Chung16c8d8a2011-01-20 16:19:33 -0800228 public synchronized boolean isConnected() {
229 return mIsConnected;
Winson Chung499cb9f2010-07-16 11:18:17 -0700230 }
231 }
232
233 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700234 * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
235 * they are loaded.
Winson Chung499cb9f2010-07-16 11:18:17 -0700236 */
Winson Chung3ec9a452010-09-23 16:40:28 -0700237 private class RemoteViewsFrameLayout extends FrameLayout {
238 public RemoteViewsFrameLayout(Context context) {
239 super(context);
Winson Chung499cb9f2010-07-16 11:18:17 -0700240 }
241
242 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700243 * Updates this RemoteViewsFrameLayout depending on the view that was loaded.
244 * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
245 * successfully.
Winson Chung499cb9f2010-07-16 11:18:17 -0700246 */
Winson Chung3ec9a452010-09-23 16:40:28 -0700247 public void onRemoteViewsLoaded(RemoteViews view) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700248 try {
249 // Remove all the children of this layout first
250 removeAllViews();
251 addView(view.apply(getContext(), this));
252 } catch (Exception e) {
253 Log.e(TAG, "Failed to apply RemoteViews.");
254 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700255 }
256 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700257
Winson Chung3ec9a452010-09-23 16:40:28 -0700258 /**
259 * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
260 * adapter that have not yet had their RemoteViews loaded.
261 */
262 private class RemoteViewsFrameLayoutRefSet {
263 private HashMap<Integer, LinkedList<RemoteViewsFrameLayout>> mReferences;
264
265 public RemoteViewsFrameLayoutRefSet() {
266 mReferences = new HashMap<Integer, LinkedList<RemoteViewsFrameLayout>>();
267 }
268
269 /**
270 * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
271 */
272 public void add(int position, RemoteViewsFrameLayout layout) {
273 final Integer pos = position;
274 LinkedList<RemoteViewsFrameLayout> refs;
275
276 // Create the list if necessary
277 if (mReferences.containsKey(pos)) {
278 refs = mReferences.get(pos);
279 } else {
280 refs = new LinkedList<RemoteViewsFrameLayout>();
281 mReferences.put(pos, refs);
Winson Chung499cb9f2010-07-16 11:18:17 -0700282 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700283
284 // Add the references to the list
285 refs.add(layout);
Winson Chung499cb9f2010-07-16 11:18:17 -0700286 }
287
Winson Chung3ec9a452010-09-23 16:40:28 -0700288 /**
289 * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
290 * the associated RemoteViews has loaded.
291 */
292 public void notifyOnRemoteViewsLoaded(int position, RemoteViews view, int typeId) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700293 if (view == null) return;
294
Winson Chung3ec9a452010-09-23 16:40:28 -0700295 final Integer pos = position;
296 if (mReferences.containsKey(pos)) {
297 // Notify all the references for that position of the newly loaded RemoteViews
298 final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos);
299 for (final RemoteViewsFrameLayout ref : refs) {
300 ref.onRemoteViewsLoaded(view);
Winson Chung499cb9f2010-07-16 11:18:17 -0700301 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700302 refs.clear();
303
304 // Remove this set from the original mapping
305 mReferences.remove(pos);
Winson Chung499cb9f2010-07-16 11:18:17 -0700306 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700307 }
308
Winson Chung3ec9a452010-09-23 16:40:28 -0700309 /**
310 * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
311 */
312 public void clear() {
313 // We currently just clear the references, and leave all the previous layouts returned
314 // in their default state of the loading view.
315 mReferences.clear();
316 }
317 }
318
319 /**
320 * The meta-data associated with the cache in it's current state.
321 */
322 private class RemoteViewsMetaData {
323 int count;
324 int viewTypeCount;
325 boolean hasStableIds;
Winson Chung3ec9a452010-09-23 16:40:28 -0700326
327 // Used to determine how to construct loading views. If a loading view is not specified
328 // by the user, then we try and load the first view, and use its height as the height for
329 // the default loading view.
330 RemoteViews mUserLoadingView;
331 RemoteViews mFirstView;
332 int mFirstViewHeight;
333
334 // A mapping from type id to a set of unique type ids
Winson Chung16c8d8a2011-01-20 16:19:33 -0800335 private final HashMap<Integer, Integer> mTypeIdIndexMap = new HashMap<Integer, Integer>();
Winson Chung3ec9a452010-09-23 16:40:28 -0700336
337 public RemoteViewsMetaData() {
338 reset();
Winson Chung499cb9f2010-07-16 11:18:17 -0700339 }
340
Winson Chung16c8d8a2011-01-20 16:19:33 -0800341 public void set(RemoteViewsMetaData d) {
342 synchronized (d) {
343 count = d.count;
344 viewTypeCount = d.viewTypeCount;
345 hasStableIds = d.hasStableIds;
346 setLoadingViewTemplates(d.mUserLoadingView, d.mFirstView);
347 }
348 }
349
Winson Chung3ec9a452010-09-23 16:40:28 -0700350 public void reset() {
351 count = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800352
Winson Chung3ec9a452010-09-23 16:40:28 -0700353 // by default there is at least one dummy view type
354 viewTypeCount = 1;
355 hasStableIds = true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700356 mUserLoadingView = null;
357 mFirstView = null;
358 mFirstViewHeight = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800359 mTypeIdIndexMap.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700360 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700361
Winson Chung3ec9a452010-09-23 16:40:28 -0700362 public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
363 mUserLoadingView = loadingView;
364 if (firstView != null) {
365 mFirstView = firstView;
366 mFirstViewHeight = -1;
367 }
368 }
Winson Chungfbc35902010-09-09 16:45:06 -0700369
Winson Chung3ec9a452010-09-23 16:40:28 -0700370 public int getMappedViewType(int typeId) {
371 if (mTypeIdIndexMap.containsKey(typeId)) {
372 return mTypeIdIndexMap.get(typeId);
373 } else {
374 // We +1 because the loading view always has view type id of 0
375 int incrementalTypeId = mTypeIdIndexMap.size() + 1;
376 mTypeIdIndexMap.put(typeId, incrementalTypeId);
377 return incrementalTypeId;
378 }
379 }
380
381 private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
382 ViewGroup parent) {
383 // Create and return a new FrameLayout, and setup the references for this position
384 final Context context = parent.getContext();
385 RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
386
387 // Create a new loading view
388 synchronized (mCache) {
389 if (mUserLoadingView != null) {
390 // A user-specified loading view
391 View loadingView = mUserLoadingView.apply(parent.getContext(), parent);
Adam Cohena32edd42010-10-26 10:35:01 -0700392 loadingView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(0));
Winson Chung3ec9a452010-09-23 16:40:28 -0700393 layout.addView(loadingView);
394 } else {
395 // A default loading view
396 // Use the size of the first row as a guide for the size of the loading view
397 if (mFirstViewHeight < 0) {
398 View firstView = mFirstView.apply(parent.getContext(), parent);
399 firstView.measure(
400 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
401 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
402 mFirstViewHeight = firstView.getMeasuredHeight();
Winson Chungfbc35902010-09-09 16:45:06 -0700403 mFirstView = null;
Winson Chung499cb9f2010-07-16 11:18:17 -0700404 }
405
Winson Chung3ec9a452010-09-23 16:40:28 -0700406 // Compose the loading view text
Winson Chunga5f6f802010-09-29 09:24:21 -0700407 TextView loadingTextView = (TextView) mLayoutInflater.inflate(
Adam Cohenfb603862010-12-17 12:03:17 -0800408 com.android.internal.R.layout.remote_views_adapter_default_loading_view,
409 layout, false);
Winson Chunga5f6f802010-09-29 09:24:21 -0700410 loadingTextView.setHeight(mFirstViewHeight);
411 loadingTextView.setTag(new Integer(0));
Winson Chung3ec9a452010-09-23 16:40:28 -0700412
Winson Chunga5f6f802010-09-29 09:24:21 -0700413 layout.addView(loadingTextView);
Winson Chung499cb9f2010-07-16 11:18:17 -0700414 }
415 }
416
Winson Chung3ec9a452010-09-23 16:40:28 -0700417 return layout;
418 }
419 }
420
421 /**
422 * The meta-data associated with a single item in the cache.
423 */
424 private class RemoteViewsIndexMetaData {
425 int typeId;
426 long itemId;
Winson Chungb90a91c2011-01-26 13:36:34 -0800427 boolean isRequested;
Winson Chung3ec9a452010-09-23 16:40:28 -0700428
Winson Chungb90a91c2011-01-26 13:36:34 -0800429 public RemoteViewsIndexMetaData(RemoteViews v, long itemId, boolean requested) {
430 set(v, itemId, requested);
Winson Chung3ec9a452010-09-23 16:40:28 -0700431 }
432
Winson Chungb90a91c2011-01-26 13:36:34 -0800433 public void set(RemoteViews v, long id, boolean requested) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700434 itemId = id;
435 if (v != null)
436 typeId = v.getLayoutId();
437 else
438 typeId = 0;
Winson Chungb90a91c2011-01-26 13:36:34 -0800439 isRequested = requested;
Winson Chung3ec9a452010-09-23 16:40:28 -0700440 }
441 }
442
443 /**
444 *
445 */
446 private class FixedSizeRemoteViewsCache {
447 private static final String TAG = "FixedSizeRemoteViewsCache";
448
449 // The meta data related to all the RemoteViews, ie. count, is stable, etc.
450 private RemoteViewsMetaData mMetaData;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800451 private RemoteViewsMetaData mTemporaryMetaData;
Winson Chung3ec9a452010-09-23 16:40:28 -0700452
453 // The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
454 // greater than or equal to the set of RemoteViews.
455 // Note: The reason that we keep this separate from the RemoteViews cache below is that this
456 // we still need to be able to access the mapping of position to meta data, without keeping
457 // the heavy RemoteViews around. The RemoteViews cache is trimmed to fixed constraints wrt.
458 // memory and size, but this metadata cache will retain information until the data at the
459 // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
460 private HashMap<Integer, RemoteViewsIndexMetaData> mIndexMetaData;
461
462 // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
463 // too much memory.
464 private HashMap<Integer, RemoteViews> mIndexRemoteViews;
465
466 // The set of indices that have been explicitly requested by the collection view
467 private HashSet<Integer> mRequestedIndices;
468
Winson Chungb90a91c2011-01-26 13:36:34 -0800469 // We keep a reference of the last requested index to determine which item to prune the
470 // farthest items from when we hit the memory limit
471 private int mLastRequestedIndex;
472
Winson Chung3ec9a452010-09-23 16:40:28 -0700473 // The set of indices to load, including those explicitly requested, as well as those
474 // determined by the preloading algorithm to be prefetched
475 private HashSet<Integer> mLoadIndices;
476
477 // The lower and upper bounds of the preloaded range
478 private int mPreloadLowerBound;
479 private int mPreloadUpperBound;
480
481 // The bounds of this fixed cache, we will try and fill as many items into the cache up to
482 // the maxCount number of items, or the maxSize memory usage.
483 // The maxCountSlack is used to determine if a new position in the cache to be loaded is
484 // sufficiently ouside the old set, prompting a shifting of the "window" of items to be
485 // preloaded.
486 private int mMaxCount;
487 private int mMaxCountSlack;
488 private static final float sMaxCountSlackPercent = 0.75f;
Winson Chungb90a91c2011-01-26 13:36:34 -0800489 private static final int sMaxMemoryLimitInBytes = 2 * 1024 * 1024;
Winson Chung3ec9a452010-09-23 16:40:28 -0700490
491 public FixedSizeRemoteViewsCache(int maxCacheSize) {
492 mMaxCount = maxCacheSize;
493 mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
494 mPreloadLowerBound = 0;
495 mPreloadUpperBound = -1;
496 mMetaData = new RemoteViewsMetaData();
Winson Chung16c8d8a2011-01-20 16:19:33 -0800497 mTemporaryMetaData = new RemoteViewsMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700498 mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
499 mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
500 mRequestedIndices = new HashSet<Integer>();
Winson Chungb90a91c2011-01-26 13:36:34 -0800501 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700502 mLoadIndices = new HashSet<Integer>();
503 }
504
Winson Chungb90a91c2011-01-26 13:36:34 -0800505 public void insert(int position, RemoteViews v, long itemId, boolean isRequested) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700506 // Trim the cache if we go beyond the count
507 if (mIndexRemoteViews.size() >= mMaxCount) {
508 mIndexRemoteViews.remove(getFarthestPositionFrom(position));
Winson Chung499cb9f2010-07-16 11:18:17 -0700509 }
510
Winson Chung3ec9a452010-09-23 16:40:28 -0700511 // Trim the cache if we go beyond the available memory size constraints
Winson Chungb90a91c2011-01-26 13:36:34 -0800512 int pruneFromPosition = (mLastRequestedIndex > -1) ? mLastRequestedIndex : position;
513 while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryLimitInBytes) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700514 // Note: This is currently the most naive mechanism for deciding what to prune when
515 // we hit the memory limit. In the future, we may want to calculate which index to
516 // remove based on both its position as well as it's current memory usage, as well
517 // as whether it was directly requested vs. whether it was preloaded by our caching
518 // mechanism.
Winson Chungb90a91c2011-01-26 13:36:34 -0800519 mIndexRemoteViews.remove(getFarthestPositionFrom(pruneFromPosition));
Winson Chung3ec9a452010-09-23 16:40:28 -0700520 }
521
522 // Update the metadata cache
523 if (mIndexMetaData.containsKey(position)) {
524 final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
Winson Chungb90a91c2011-01-26 13:36:34 -0800525 metaData.set(v, itemId, isRequested);
Winson Chung3ec9a452010-09-23 16:40:28 -0700526 } else {
Winson Chungb90a91c2011-01-26 13:36:34 -0800527 mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId, isRequested));
Winson Chung3ec9a452010-09-23 16:40:28 -0700528 }
529 mIndexRemoteViews.put(position, v);
530 }
531
532 public RemoteViewsMetaData getMetaData() {
533 return mMetaData;
534 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800535 public RemoteViewsMetaData getTemporaryMetaData() {
536 return mTemporaryMetaData;
537 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700538 public RemoteViews getRemoteViewsAt(int position) {
539 if (mIndexRemoteViews.containsKey(position)) {
540 return mIndexRemoteViews.get(position);
541 }
542 return null;
543 }
544 public RemoteViewsIndexMetaData getMetaDataAt(int position) {
545 if (mIndexMetaData.containsKey(position)) {
546 return mIndexMetaData.get(position);
547 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700548 return null;
549 }
550
Winson Chung16c8d8a2011-01-20 16:19:33 -0800551 public void commitTemporaryMetaData() {
552 synchronized (mTemporaryMetaData) {
553 synchronized (mMetaData) {
554 mMetaData.set(mTemporaryMetaData);
555 }
556 }
557 }
558
Winson Chung3ec9a452010-09-23 16:40:28 -0700559 private int getRemoteViewsBitmapMemoryUsage() {
560 // Calculate the memory usage of all the RemoteViews bitmaps being cached
561 int mem = 0;
562 for (Integer i : mIndexRemoteViews.keySet()) {
563 final RemoteViews v = mIndexRemoteViews.get(i);
Winson Chungaaffa8b2010-10-30 14:04:05 -0700564 if (v != null) {
565 mem += v.estimateBitmapMemoryUsage();
566 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700567 }
568 return mem;
569 }
570 private int getFarthestPositionFrom(int pos) {
571 // Find the index farthest away and remove that
572 int maxDist = 0;
573 int maxDistIndex = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800574 int maxDistNonRequested = 0;
575 int maxDistIndexNonRequested = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700576 for (int i : mIndexRemoteViews.keySet()) {
577 int dist = Math.abs(i-pos);
Winson Chungb90a91c2011-01-26 13:36:34 -0800578 if (dist > maxDistNonRequested && !mIndexMetaData.get(i).isRequested) {
579 // maxDistNonRequested/maxDistIndexNonRequested will store the index of the
580 // farthest non-requested position
581 maxDistIndexNonRequested = i;
582 maxDistNonRequested = dist;
583 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700584 if (dist > maxDist) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800585 // maxDist/maxDistIndex will store the index of the farthest position
586 // regardless of whether it was directly requested or not
Winson Chung3ec9a452010-09-23 16:40:28 -0700587 maxDistIndex = i;
588 maxDist = dist;
Winson Chung499cb9f2010-07-16 11:18:17 -0700589 }
590 }
Winson Chungb90a91c2011-01-26 13:36:34 -0800591 if (maxDistIndexNonRequested > -1) {
592 return maxDistIndexNonRequested;
593 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700594 return maxDistIndex;
Winson Chung499cb9f2010-07-16 11:18:17 -0700595 }
596
Winson Chung3ec9a452010-09-23 16:40:28 -0700597 public void queueRequestedPositionToLoad(int position) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800598 mLastRequestedIndex = position;
Winson Chung3ec9a452010-09-23 16:40:28 -0700599 synchronized (mLoadIndices) {
600 mRequestedIndices.add(position);
601 mLoadIndices.add(position);
602 }
603 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800604 public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700605 // Check if we need to preload any items
606 if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
607 int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
608 if (Math.abs(position - center) < mMaxCountSlack) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800609 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700610 }
611 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700612
Winson Chung3ec9a452010-09-23 16:40:28 -0700613 int count = 0;
614 synchronized (mMetaData) {
615 count = mMetaData.count;
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700616 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700617 synchronized (mLoadIndices) {
618 mLoadIndices.clear();
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700619
Winson Chung3ec9a452010-09-23 16:40:28 -0700620 // Add all the requested indices
621 mLoadIndices.addAll(mRequestedIndices);
Winson Chung499cb9f2010-07-16 11:18:17 -0700622
Winson Chung3ec9a452010-09-23 16:40:28 -0700623 // Add all the preload indices
624 int halfMaxCount = mMaxCount / 2;
625 mPreloadLowerBound = position - halfMaxCount;
626 mPreloadUpperBound = position + halfMaxCount;
627 int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
628 int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
629 for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
630 mLoadIndices.add(i);
Winson Chung499cb9f2010-07-16 11:18:17 -0700631 }
632
Winson Chung3ec9a452010-09-23 16:40:28 -0700633 // But remove all the indices that have already been loaded and are cached
634 mLoadIndices.removeAll(mIndexRemoteViews.keySet());
635 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800636 return true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700637 }
Winson Chungb90a91c2011-01-26 13:36:34 -0800638 /** Returns the next index to load, and whether that index was directly requested or not */
639 public int[] getNextIndexToLoad() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700640 // We try and prioritize items that have been requested directly, instead
641 // of items that are loaded as a result of the caching mechanism
642 synchronized (mLoadIndices) {
643 // Prioritize requested indices to be loaded first
644 if (!mRequestedIndices.isEmpty()) {
645 Integer i = mRequestedIndices.iterator().next();
646 mRequestedIndices.remove(i);
647 mLoadIndices.remove(i);
Winson Chungb90a91c2011-01-26 13:36:34 -0800648 return new int[]{i.intValue(), 1};
Winson Chung3ec9a452010-09-23 16:40:28 -0700649 }
650
651 // Otherwise, preload other indices as necessary
652 if (!mLoadIndices.isEmpty()) {
653 Integer i = mLoadIndices.iterator().next();
654 mLoadIndices.remove(i);
Winson Chungb90a91c2011-01-26 13:36:34 -0800655 return new int[]{i.intValue(), 0};
Winson Chung3ec9a452010-09-23 16:40:28 -0700656 }
657
Winson Chungb90a91c2011-01-26 13:36:34 -0800658 return new int[]{-1, 0};
Winson Chung3ec9a452010-09-23 16:40:28 -0700659 }
660 }
661
662 public boolean containsRemoteViewAt(int position) {
663 return mIndexRemoteViews.containsKey(position);
664 }
665 public boolean containsMetaDataAt(int position) {
666 return mIndexMetaData.containsKey(position);
667 }
668
669 public void reset() {
Winson Chung61ac7e32010-09-28 10:08:31 -0700670 // Note: We do not try and reset the meta data, since that information is still used by
671 // collection views to validate it's own contents (and will be re-requested if the data
672 // is invalidated through the notifyDataSetChanged() flow).
673
Winson Chung3ec9a452010-09-23 16:40:28 -0700674 mPreloadLowerBound = 0;
675 mPreloadUpperBound = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800676 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700677 mIndexRemoteViews.clear();
678 mIndexMetaData.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700679 synchronized (mLoadIndices) {
680 mRequestedIndices.clear();
681 mLoadIndices.clear();
Winson Chung499cb9f2010-07-16 11:18:17 -0700682 }
683 }
684 }
685
686 public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
687 mContext = context;
688 mIntent = intent;
Winson Chung81f39eb2011-01-11 18:05:01 -0800689 mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
Winson Chunga5f6f802010-09-29 09:24:21 -0700690 mLayoutInflater = LayoutInflater.from(context);
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700691 if (mIntent == null) {
692 throw new IllegalArgumentException("Non-null Intent must be specified.");
693 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700694 mRequestedViews = new RemoteViewsFrameLayoutRefSet();
Winson Chung499cb9f2010-07-16 11:18:17 -0700695
Winson Chung81f39eb2011-01-11 18:05:01 -0800696 // Strip the previously injected app widget id from service intent
697 if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
698 intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
699 }
700
701 // Initialize the worker thread
Winson Chung499cb9f2010-07-16 11:18:17 -0700702 mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
703 mWorkerThread.start();
704 mWorkerQueue = new Handler(mWorkerThread.getLooper());
Winson Chung81f39eb2011-01-11 18:05:01 -0800705 mMainQueue = new Handler(Looper.myLooper(), this);
Winson Chung499cb9f2010-07-16 11:18:17 -0700706
Winson Chung81f39eb2011-01-11 18:05:01 -0800707 // Initialize the cache and the service connection on startup
708 mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
Winson Chung3ec9a452010-09-23 16:40:28 -0700709 mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
710 mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
Winson Chung499cb9f2010-07-16 11:18:17 -0700711 requestBindService();
712 }
713
Winson Chung3ec9a452010-09-23 16:40:28 -0700714 private void loadNextIndexInBackground() {
715 mWorkerQueue.post(new Runnable() {
716 @Override
717 public void run() {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800718 if (mServiceConnection.isConnected()) {
719 // Get the next index to load
720 int position = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800721 boolean isRequested = false;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800722 synchronized (mCache) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800723 int[] res = mCache.getNextIndexToLoad();
724 position = res[0];
725 isRequested = res[1] > 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800726 }
727 if (position > -1) {
728 // Load the item, and notify any existing RemoteViewsFrameLayouts
Winson Chungb90a91c2011-01-26 13:36:34 -0800729 updateRemoteViews(position, isRequested);
Winson Chung3ec9a452010-09-23 16:40:28 -0700730
Winson Chung16c8d8a2011-01-20 16:19:33 -0800731 // Queue up for the next one to load
732 loadNextIndexInBackground();
733 } else {
734 // No more items to load, so queue unbind
735 enqueueDeferredUnbindServiceMessage();
736 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700737 }
738 }
739 });
740 }
741
Winson Chung16c8d8a2011-01-20 16:19:33 -0800742 private void processException(String method, Exception e) {
743 Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -0700744
Winson Chung16c8d8a2011-01-20 16:19:33 -0800745 // If we encounter a crash when updating, we should reset the metadata & cache and trigger
746 // a notifyDataSetChanged to update the widget accordingly
747 final RemoteViewsMetaData metaData = mCache.getMetaData();
748 synchronized (metaData) {
749 metaData.reset();
750 }
751 synchronized (mCache) {
752 mCache.reset();
753 }
754 mMainQueue.post(new Runnable() {
755 @Override
756 public void run() {
757 superNotifyDataSetChanged();
Winson Chung3ec9a452010-09-23 16:40:28 -0700758 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800759 });
760 }
761
762 private void updateTemporaryMetaData() {
763 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
764
765 try {
766 // get the properties/first view (so that we can use it to
767 // measure our dummy views)
768 boolean hasStableIds = factory.hasStableIds();
769 int viewTypeCount = factory.getViewTypeCount();
770 int count = factory.getCount();
771 RemoteViews loadingView = factory.getLoadingView();
772 RemoteViews firstView = null;
773 if ((count > 0) && (loadingView == null)) {
774 firstView = factory.getViewAt(0);
775 }
776 final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
777 synchronized (tmpMetaData) {
778 tmpMetaData.hasStableIds = hasStableIds;
779 // We +1 because the base view type is the loading view
780 tmpMetaData.viewTypeCount = viewTypeCount + 1;
781 tmpMetaData.count = count;
782 tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
783 }
Adam Cohen2625fea2011-03-23 17:24:30 -0700784 } catch(RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800785 processException("updateMetaData", e);
Adam Cohenfa2e3ff2011-04-07 16:48:19 -0700786 } catch(RuntimeException e) {
787 processException("updateMetaData", e);
Winson Chung3ec9a452010-09-23 16:40:28 -0700788 }
789 }
790
Winson Chungb90a91c2011-01-26 13:36:34 -0800791 private void updateRemoteViews(final int position, boolean isRequested) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800792 if (!mServiceConnection.isConnected()) return;
793 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
Winson Chung3ec9a452010-09-23 16:40:28 -0700794
Winson Chung16c8d8a2011-01-20 16:19:33 -0800795 // Load the item information from the remote service
796 RemoteViews remoteViews = null;
797 long itemId = 0;
798 try {
799 remoteViews = factory.getViewAt(position);
800 itemId = factory.getItemId(position);
Adam Cohen2625fea2011-03-23 17:24:30 -0700801 } catch (RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800802 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -0700803
Winson Chung16c8d8a2011-01-20 16:19:33 -0800804 // Return early to prevent additional work in re-centering the view cache, and
805 // swapping from the loading view
806 return;
Adam Cohen2625fea2011-03-23 17:24:30 -0700807 } catch (RuntimeException e) {
808 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
809 return;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800810 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700811
Winson Chung16c8d8a2011-01-20 16:19:33 -0800812 if (remoteViews == null) {
813 // If a null view was returned, we break early to prevent it from getting
814 // into our cache and causing problems later. The effect is that the child at this
815 // position will remain as a loading view until it is updated.
816 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
817 "returned from RemoteViewsFactory.");
818 return;
819 }
820 synchronized (mCache) {
821 // Cache the RemoteViews we loaded
Winson Chungb90a91c2011-01-26 13:36:34 -0800822 mCache.insert(position, remoteViews, itemId, isRequested);
Winson Chung3ec9a452010-09-23 16:40:28 -0700823
Winson Chung16c8d8a2011-01-20 16:19:33 -0800824 // Notify all the views that we have previously returned for this index that
825 // there is new data for it.
826 final RemoteViews rv = remoteViews;
827 final int typeId = mCache.getMetaDataAt(position).typeId;
828 mMainQueue.post(new Runnable() {
829 @Override
830 public void run() {
831 mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
832 }
833 });
Winson Chung3ec9a452010-09-23 16:40:28 -0700834 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700835 }
836
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700837 public Intent getRemoteViewsServiceIntent() {
838 return mIntent;
839 }
840
Winson Chung499cb9f2010-07-16 11:18:17 -0700841 public int getCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700842 final RemoteViewsMetaData metaData = mCache.getMetaData();
843 synchronized (metaData) {
844 return metaData.count;
845 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700846 }
847
848 public Object getItem(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700849 // Disallow arbitrary object to be associated with an item for the time being
Winson Chung499cb9f2010-07-16 11:18:17 -0700850 return null;
851 }
852
853 public long getItemId(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700854 synchronized (mCache) {
855 if (mCache.containsMetaDataAt(position)) {
856 return mCache.getMetaDataAt(position).itemId;
857 }
858 return 0;
859 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700860 }
861
862 public int getItemViewType(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700863 int typeId = 0;
864 synchronized (mCache) {
865 if (mCache.containsMetaDataAt(position)) {
866 typeId = mCache.getMetaDataAt(position).typeId;
867 } else {
868 return 0;
869 }
870 }
871
872 final RemoteViewsMetaData metaData = mCache.getMetaData();
873 synchronized (metaData) {
874 return metaData.getMappedViewType(typeId);
875 }
876 }
877
878 /**
879 * Returns the item type id for the specified convert view. Returns -1 if the convert view
880 * is invalid.
881 */
882 private int getConvertViewTypeId(View convertView) {
883 int typeId = -1;
Adam Cohena32edd42010-10-26 10:35:01 -0700884 if (convertView != null) {
885 Object tag = convertView.getTag(com.android.internal.R.id.rowTypeId);
886 if (tag != null) {
887 typeId = (Integer) tag;
888 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700889 }
890 return typeId;
Winson Chung499cb9f2010-07-16 11:18:17 -0700891 }
892
893 public View getView(int position, View convertView, ViewGroup parent) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800894 // "Request" an index so that we can queue it for loading, initiate subsequent
895 // preloading, etc.
896 synchronized (mCache) {
897 boolean isInCache = mCache.containsRemoteViewAt(position);
898 boolean isConnected = mServiceConnection.isConnected();
899 boolean hasNewItems = false;
900
Winson Chung7ab73e72011-03-04 15:47:23 -0800901 if (!isInCache && !isConnected) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800902 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
903 // in turn trigger another request to getView()
904 requestBindService();
905 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700906 // Queue up other indices to be preloaded based on this position
Winson Chung16c8d8a2011-01-20 16:19:33 -0800907 hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
908 }
909
910 if (isInCache) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700911 View convertViewChild = null;
912 int convertViewTypeId = 0;
Adam Cohen181d2e32011-01-17 12:40:29 -0800913 RemoteViewsFrameLayout layout = null;
914
915 if (convertView instanceof RemoteViewsFrameLayout) {
916 layout = (RemoteViewsFrameLayout) convertView;
Winson Chung3ec9a452010-09-23 16:40:28 -0700917 convertViewChild = layout.getChildAt(0);
918 convertViewTypeId = getConvertViewTypeId(convertViewChild);
919 }
920
921 // Second, we try and retrieve the RemoteViews from the cache, returning a loading
922 // view and queueing it to be loaded if it has not already been loaded.
Winson Chung16c8d8a2011-01-20 16:19:33 -0800923 Context context = parent.getContext();
924 RemoteViews rv = mCache.getRemoteViewsAt(position);
Adam Cohenaeb66ca2011-02-10 16:08:12 -0800925 RemoteViewsIndexMetaData indexMetaData = mCache.getMetaDataAt(position);
926 indexMetaData.isRequested = true;
927 int typeId = indexMetaData.typeId;
Winson Chung3ec9a452010-09-23 16:40:28 -0700928
Winson Chung16c8d8a2011-01-20 16:19:33 -0800929 // Reuse the convert view where possible
930 if (layout != null) {
931 if (convertViewTypeId == typeId) {
932 rv.reapply(context, convertViewChild);
933 return layout;
Winson Chung3ec9a452010-09-23 16:40:28 -0700934 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800935 layout.removeAllViews();
Winson Chung3ec9a452010-09-23 16:40:28 -0700936 } else {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800937 layout = new RemoteViewsFrameLayout(context);
Winson Chung3ec9a452010-09-23 16:40:28 -0700938 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800939
940 // Otherwise, create a new view to be returned
941 View newView = rv.apply(context, parent);
942 newView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(typeId));
943 layout.addView(newView);
944 if (hasNewItems) loadNextIndexInBackground();
945
946 return layout;
947 } else {
948 // If the cache does not have the RemoteViews at this position, then create a
949 // loading view and queue the actual position to be loaded in the background
950 RemoteViewsFrameLayout loadingView = null;
951 final RemoteViewsMetaData metaData = mCache.getMetaData();
952 synchronized (metaData) {
953 loadingView = metaData.createLoadingView(position, convertView, parent);
954 }
955
956 mRequestedViews.add(position, loadingView);
957 mCache.queueRequestedPositionToLoad(position);
958 loadNextIndexInBackground();
959
960 return loadingView;
Winson Chung3ec9a452010-09-23 16:40:28 -0700961 }
962 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700963 }
964
965 public int getViewTypeCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700966 final RemoteViewsMetaData metaData = mCache.getMetaData();
967 synchronized (metaData) {
968 return metaData.viewTypeCount;
969 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700970 }
971
972 public boolean hasStableIds() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700973 final RemoteViewsMetaData metaData = mCache.getMetaData();
974 synchronized (metaData) {
975 return metaData.hasStableIds;
976 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700977 }
978
979 public boolean isEmpty() {
980 return getCount() <= 0;
981 }
982
Winson Chung16c8d8a2011-01-20 16:19:33 -0800983 private void onNotifyDataSetChanged() {
984 // Complete the actual notifyDataSetChanged() call initiated earlier
985 IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
986 try {
987 factory.onDataSetChanged();
Adam Cohen2625fea2011-03-23 17:24:30 -0700988 } catch (RemoteException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800989 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
990
991 // Return early to prevent from further being notified (since nothing has
992 // changed)
993 return;
Adam Cohen2625fea2011-03-23 17:24:30 -0700994 } catch (RuntimeException e) {
995 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
996 return;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800997 }
998
999 // Flush the cache so that we can reload new items from the service
1000 synchronized (mCache) {
1001 mCache.reset();
1002 }
1003
1004 // Re-request the new metadata (only after the notification to the factory)
1005 updateTemporaryMetaData();
1006
1007 // Propagate the notification back to the base adapter
1008 mMainQueue.post(new Runnable() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001009 @Override
1010 public void run() {
Winson Chung6364f2b2010-09-29 11:14:30 -07001011 synchronized (mCache) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001012 mCache.commitTemporaryMetaData();
Winson Chung6364f2b2010-09-29 11:14:30 -07001013 }
1014
Winson Chung16c8d8a2011-01-20 16:19:33 -08001015 superNotifyDataSetChanged();
1016 enqueueDeferredUnbindServiceMessage();
Winson Chung3ec9a452010-09-23 16:40:28 -07001017 }
1018 });
Winson Chung6364f2b2010-09-29 11:14:30 -07001019
Winson Chung16c8d8a2011-01-20 16:19:33 -08001020 // Reset the notify flagflag
1021 mNotifyDataSetChangedAfterOnServiceConnected = false;
1022 }
1023
1024 public void notifyDataSetChanged() {
1025 // Dequeue any unbind messages
1026 mMainQueue.removeMessages(sUnbindServiceMessageType);
1027
1028 // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
1029 // connect
1030 if (!mServiceConnection.isConnected()) {
1031 if (mNotifyDataSetChangedAfterOnServiceConnected) {
1032 return;
1033 }
1034
1035 mNotifyDataSetChangedAfterOnServiceConnected = true;
1036 requestBindService();
1037 return;
1038 }
1039
1040 mWorkerQueue.post(new Runnable() {
1041 @Override
1042 public void run() {
1043 onNotifyDataSetChanged();
1044 }
1045 });
Winson Chung3ec9a452010-09-23 16:40:28 -07001046 }
1047
Adam Cohenfb603862010-12-17 12:03:17 -08001048 void superNotifyDataSetChanged() {
Winson Chung499cb9f2010-07-16 11:18:17 -07001049 super.notifyDataSetChanged();
1050 }
1051
Winson Chung81f39eb2011-01-11 18:05:01 -08001052 @Override
1053 public boolean handleMessage(Message msg) {
1054 boolean result = false;
1055 switch (msg.what) {
1056 case sUnbindServiceMessageType:
Winson Chung81f39eb2011-01-11 18:05:01 -08001057 if (mServiceConnection.isConnected()) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001058 mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
Winson Chung81f39eb2011-01-11 18:05:01 -08001059 }
1060 result = true;
1061 break;
1062 default:
1063 break;
1064 }
1065 return result;
1066 }
1067
1068 private void enqueueDeferredUnbindServiceMessage() {
1069 // Remove any existing deferred-unbind messages
1070 mMainQueue.removeMessages(sUnbindServiceMessageType);
1071 mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
1072 }
1073
Winson Chung499cb9f2010-07-16 11:18:17 -07001074 private boolean requestBindService() {
Winson Chung81f39eb2011-01-11 18:05:01 -08001075 // Try binding the service (which will start it if it's not already running)
Winson Chung499cb9f2010-07-16 11:18:17 -07001076 if (!mServiceConnection.isConnected()) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001077 mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
Winson Chung499cb9f2010-07-16 11:18:17 -07001078 }
1079
Winson Chung16c8d8a2011-01-20 16:19:33 -08001080 // Remove any existing deferred-unbind messages
1081 mMainQueue.removeMessages(sUnbindServiceMessageType);
Winson Chung499cb9f2010-07-16 11:18:17 -07001082 return mServiceConnection.isConnected();
1083 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001084}