• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.widget;
18 
19 import java.lang.ref.WeakReference;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.LinkedList;
24 
25 import android.Manifest;
26 import android.appwidget.AppWidgetManager;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.PackageManager;
30 import android.os.Handler;
31 import android.os.HandlerThread;
32 import android.os.IBinder;
33 import android.os.Looper;
34 import android.os.Message;
35 import android.os.Process;
36 import android.os.RemoteException;
37 import android.os.UserHandle;
38 import android.util.Log;
39 import android.util.Slog;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 import android.view.View.MeasureSpec;
43 import android.view.ViewGroup;
44 import android.widget.RemoteViews.OnClickHandler;
45 
46 import com.android.internal.widget.IRemoteViewsAdapterConnection;
47 import com.android.internal.widget.IRemoteViewsFactory;
48 import com.android.internal.widget.LockPatternUtils;
49 
50 /**
51  * An adapter to a RemoteViewsService which fetches and caches RemoteViews
52  * to be later inflated as child views.
53  */
54 /** @hide */
55 public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
56     private static final String MULTI_USER_PERM = Manifest.permission.INTERACT_ACROSS_USERS_FULL;
57 
58     private static final String TAG = "RemoteViewsAdapter";
59 
60     // The max number of items in the cache
61     private static final int sDefaultCacheSize = 40;
62     // The delay (in millis) to wait until attempting to unbind from a service after a request.
63     // This ensures that we don't stay continually bound to the service and that it can be destroyed
64     // if we need the memory elsewhere in the system.
65     private static final int sUnbindServiceDelay = 5000;
66 
67     // Default height for the default loading view, in case we cannot get inflate the first view
68     private static final int sDefaultLoadingViewHeight = 50;
69 
70     // Type defs for controlling different messages across the main and worker message queues
71     private static final int sDefaultMessageType = 0;
72     private static final int sUnbindServiceMessageType = 1;
73 
74     private final Context mContext;
75     private final Intent mIntent;
76     private final int mAppWidgetId;
77     private LayoutInflater mLayoutInflater;
78     private RemoteViewsAdapterServiceConnection mServiceConnection;
79     private WeakReference<RemoteAdapterConnectionCallback> mCallback;
80     private OnClickHandler mRemoteViewsOnClickHandler;
81     private FixedSizeRemoteViewsCache mCache;
82     private int mVisibleWindowLowerBound;
83     private int mVisibleWindowUpperBound;
84 
85     // A flag to determine whether we should notify data set changed after we connect
86     private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
87 
88     // The set of requested views that are to be notified when the associated RemoteViews are
89     // loaded.
90     private RemoteViewsFrameLayoutRefSet mRequestedViews;
91 
92     private HandlerThread mWorkerThread;
93     // items may be interrupted within the normally processed queues
94     private Handler mWorkerQueue;
95     private Handler mMainQueue;
96 
97     // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
98     // structures;
99     private static final HashMap<RemoteViewsCacheKey,
100             FixedSizeRemoteViewsCache> sCachedRemoteViewsCaches
101             = new HashMap<RemoteViewsCacheKey,
102                     FixedSizeRemoteViewsCache>();
103     private static final HashMap<RemoteViewsCacheKey, Runnable>
104             sRemoteViewsCacheRemoveRunnables
105             = new HashMap<RemoteViewsCacheKey, Runnable>();
106 
107     private static HandlerThread sCacheRemovalThread;
108     private static Handler sCacheRemovalQueue;
109 
110     // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation.
111     // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this
112     // duration, the cache is dropped.
113     private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
114 
115     // Used to indicate to the AdapterView that it can use this Adapter immediately after
116     // construction (happens when we have a cached FixedSizeRemoteViewsCache).
117     private boolean mDataReady = false;
118 
119     int mUserId;
120 
121     /**
122      * An interface for the RemoteAdapter to notify other classes when adapters
123      * are actually connected to/disconnected from their actual services.
124      */
125     public interface RemoteAdapterConnectionCallback {
126         /**
127          * @return whether the adapter was set or not.
128          */
onRemoteAdapterConnected()129         public boolean onRemoteAdapterConnected();
130 
onRemoteAdapterDisconnected()131         public void onRemoteAdapterDisconnected();
132 
133         /**
134          * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
135          * connected yet.
136          */
deferNotifyDataSetChanged()137         public void deferNotifyDataSetChanged();
138     }
139 
140     /**
141      * The service connection that gets populated when the RemoteViewsService is
142      * bound.  This must be a static inner class to ensure that no references to the outer
143      * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
144      * garbage collected, and would cause us to leak activities due to the caching mechanism for
145      * FrameLayouts in the adapter).
146      */
147     private static class RemoteViewsAdapterServiceConnection extends
148             IRemoteViewsAdapterConnection.Stub {
149         private boolean mIsConnected;
150         private boolean mIsConnecting;
151         private WeakReference<RemoteViewsAdapter> mAdapter;
152         private IRemoteViewsFactory mRemoteViewsFactory;
153 
RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter)154         public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
155             mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
156         }
157 
bind(Context context, int appWidgetId, Intent intent)158         public synchronized void bind(Context context, int appWidgetId, Intent intent) {
159             if (!mIsConnecting) {
160                 try {
161                     RemoteViewsAdapter adapter;
162                     final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
163                     if ((adapter = mAdapter.get()) != null) {
164                         checkInteractAcrossUsersPermission(context, adapter.mUserId);
165                         mgr.bindRemoteViewsService(appWidgetId, intent, asBinder(),
166                                 new UserHandle(adapter.mUserId));
167                     } else {
168                         Slog.w(TAG, "bind: adapter was null");
169                     }
170                     mIsConnecting = true;
171                 } catch (Exception e) {
172                     Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
173                     mIsConnecting = false;
174                     mIsConnected = false;
175                 }
176             }
177         }
178 
unbind(Context context, int appWidgetId, Intent intent)179         public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
180             try {
181                 RemoteViewsAdapter adapter;
182                 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
183                 if ((adapter = mAdapter.get()) != null) {
184                     checkInteractAcrossUsersPermission(context, adapter.mUserId);
185                     mgr.unbindRemoteViewsService(appWidgetId, intent,
186                             new UserHandle(adapter.mUserId));
187                 } else {
188                     Slog.w(TAG, "unbind: adapter was null");
189                 }
190                 mIsConnecting = false;
191             } catch (Exception e) {
192                 Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
193                 mIsConnecting = false;
194                 mIsConnected = false;
195             }
196         }
197 
onServiceConnected(IBinder service)198         public synchronized void onServiceConnected(IBinder service) {
199             mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
200 
201             // Remove any deferred unbind messages
202             final RemoteViewsAdapter adapter = mAdapter.get();
203             if (adapter == null) return;
204 
205             // Queue up work that we need to do for the callback to run
206             adapter.mWorkerQueue.post(new Runnable() {
207                 @Override
208                 public void run() {
209                     if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
210                         // Handle queued notifyDataSetChanged() if necessary
211                         adapter.onNotifyDataSetChanged();
212                     } else {
213                         IRemoteViewsFactory factory =
214                             adapter.mServiceConnection.getRemoteViewsFactory();
215                         try {
216                             if (!factory.isCreated()) {
217                                 // We only call onDataSetChanged() if this is the factory was just
218                                 // create in response to this bind
219                                 factory.onDataSetChanged();
220                             }
221                         } catch (RemoteException e) {
222                             Log.e(TAG, "Error notifying factory of data set changed in " +
223                                         "onServiceConnected(): " + e.getMessage());
224 
225                             // Return early to prevent anything further from being notified
226                             // (effectively nothing has changed)
227                             return;
228                         } catch (RuntimeException e) {
229                             Log.e(TAG, "Error notifying factory of data set changed in " +
230                                     "onServiceConnected(): " + e.getMessage());
231                         }
232 
233                         // Request meta data so that we have up to date data when calling back to
234                         // the remote adapter callback
235                         adapter.updateTemporaryMetaData();
236 
237                         // Notify the host that we've connected
238                         adapter.mMainQueue.post(new Runnable() {
239                             @Override
240                             public void run() {
241                                 synchronized (adapter.mCache) {
242                                     adapter.mCache.commitTemporaryMetaData();
243                                 }
244 
245                                 final RemoteAdapterConnectionCallback callback =
246                                     adapter.mCallback.get();
247                                 if (callback != null) {
248                                     callback.onRemoteAdapterConnected();
249                                 }
250                             }
251                         });
252                     }
253 
254                     // Enqueue unbind message
255                     adapter.enqueueDeferredUnbindServiceMessage();
256                     mIsConnected = true;
257                     mIsConnecting = false;
258                 }
259             });
260         }
261 
onServiceDisconnected()262         public synchronized void onServiceDisconnected() {
263             mIsConnected = false;
264             mIsConnecting = false;
265             mRemoteViewsFactory = null;
266 
267             // Clear the main/worker queues
268             final RemoteViewsAdapter adapter = mAdapter.get();
269             if (adapter == null) return;
270 
271             adapter.mMainQueue.post(new Runnable() {
272                 @Override
273                 public void run() {
274                     // Dequeue any unbind messages
275                     adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
276 
277                     final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
278                     if (callback != null) {
279                         callback.onRemoteAdapterDisconnected();
280                     }
281                 }
282             });
283         }
284 
getRemoteViewsFactory()285         public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
286             return mRemoteViewsFactory;
287         }
288 
isConnected()289         public synchronized boolean isConnected() {
290             return mIsConnected;
291         }
292     }
293 
294     /**
295      * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
296      * they are loaded.
297      */
298     private static class RemoteViewsFrameLayout extends FrameLayout {
RemoteViewsFrameLayout(Context context)299         public RemoteViewsFrameLayout(Context context) {
300             super(context);
301         }
302 
303         /**
304          * Updates this RemoteViewsFrameLayout depending on the view that was loaded.
305          * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
306          *             successfully.
307          */
onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler)308         public void onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler) {
309             try {
310                 // Remove all the children of this layout first
311                 removeAllViews();
312                 addView(view.apply(getContext(), this, handler));
313             } catch (Exception e) {
314                 Log.e(TAG, "Failed to apply RemoteViews.");
315             }
316         }
317     }
318 
319     /**
320      * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
321      * adapter that have not yet had their RemoteViews loaded.
322      */
323     private class RemoteViewsFrameLayoutRefSet {
324         private HashMap<Integer, LinkedList<RemoteViewsFrameLayout>> mReferences;
325         private HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>
326                 mViewToLinkedList;
327 
RemoteViewsFrameLayoutRefSet()328         public RemoteViewsFrameLayoutRefSet() {
329             mReferences = new HashMap<Integer, LinkedList<RemoteViewsFrameLayout>>();
330             mViewToLinkedList =
331                     new HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>();
332         }
333 
334         /**
335          * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
336          */
add(int position, RemoteViewsFrameLayout layout)337         public void add(int position, RemoteViewsFrameLayout layout) {
338             final Integer pos = position;
339             LinkedList<RemoteViewsFrameLayout> refs;
340 
341             // Create the list if necessary
342             if (mReferences.containsKey(pos)) {
343                 refs = mReferences.get(pos);
344             } else {
345                 refs = new LinkedList<RemoteViewsFrameLayout>();
346                 mReferences.put(pos, refs);
347             }
348             mViewToLinkedList.put(layout, refs);
349 
350             // Add the references to the list
351             refs.add(layout);
352         }
353 
354         /**
355          * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
356          * the associated RemoteViews has loaded.
357          */
notifyOnRemoteViewsLoaded(int position, RemoteViews view)358         public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
359             if (view == null) return;
360 
361             final Integer pos = position;
362             if (mReferences.containsKey(pos)) {
363                 // Notify all the references for that position of the newly loaded RemoteViews
364                 final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos);
365                 for (final RemoteViewsFrameLayout ref : refs) {
366                     ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler);
367                     if (mViewToLinkedList.containsKey(ref)) {
368                         mViewToLinkedList.remove(ref);
369                     }
370                 }
371                 refs.clear();
372                 // Remove this set from the original mapping
373                 mReferences.remove(pos);
374             }
375         }
376 
377         /**
378          * We need to remove views from this set if they have been recycled by the AdapterView.
379          */
removeView(RemoteViewsFrameLayout rvfl)380         public void removeView(RemoteViewsFrameLayout rvfl) {
381             if (mViewToLinkedList.containsKey(rvfl)) {
382                 mViewToLinkedList.get(rvfl).remove(rvfl);
383                 mViewToLinkedList.remove(rvfl);
384             }
385         }
386 
387         /**
388          * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
389          */
clear()390         public void clear() {
391             // We currently just clear the references, and leave all the previous layouts returned
392             // in their default state of the loading view.
393             mReferences.clear();
394             mViewToLinkedList.clear();
395         }
396     }
397 
398     /**
399      * The meta-data associated with the cache in it's current state.
400      */
401     private static class RemoteViewsMetaData {
402         int count;
403         int viewTypeCount;
404         boolean hasStableIds;
405 
406         // Used to determine how to construct loading views.  If a loading view is not specified
407         // by the user, then we try and load the first view, and use its height as the height for
408         // the default loading view.
409         RemoteViews mUserLoadingView;
410         RemoteViews mFirstView;
411         int mFirstViewHeight;
412 
413         // A mapping from type id to a set of unique type ids
414         private final HashMap<Integer, Integer> mTypeIdIndexMap = new HashMap<Integer, Integer>();
415 
RemoteViewsMetaData()416         public RemoteViewsMetaData() {
417             reset();
418         }
419 
set(RemoteViewsMetaData d)420         public void set(RemoteViewsMetaData d) {
421             synchronized (d) {
422                 count = d.count;
423                 viewTypeCount = d.viewTypeCount;
424                 hasStableIds = d.hasStableIds;
425                 setLoadingViewTemplates(d.mUserLoadingView, d.mFirstView);
426             }
427         }
428 
reset()429         public void reset() {
430             count = 0;
431 
432             // by default there is at least one dummy view type
433             viewTypeCount = 1;
434             hasStableIds = true;
435             mUserLoadingView = null;
436             mFirstView = null;
437             mFirstViewHeight = 0;
438             mTypeIdIndexMap.clear();
439         }
440 
setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView)441         public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
442             mUserLoadingView = loadingView;
443             if (firstView != null) {
444                 mFirstView = firstView;
445                 mFirstViewHeight = -1;
446             }
447         }
448 
getMappedViewType(int typeId)449         public int getMappedViewType(int typeId) {
450             if (mTypeIdIndexMap.containsKey(typeId)) {
451                 return mTypeIdIndexMap.get(typeId);
452             } else {
453                 // We +1 because the loading view always has view type id of 0
454                 int incrementalTypeId = mTypeIdIndexMap.size() + 1;
455                 mTypeIdIndexMap.put(typeId, incrementalTypeId);
456                 return incrementalTypeId;
457             }
458         }
459 
isViewTypeInRange(int typeId)460         public boolean isViewTypeInRange(int typeId) {
461             int mappedType = getMappedViewType(typeId);
462             if (mappedType >= viewTypeCount) {
463                 return false;
464             } else {
465                 return true;
466             }
467         }
468 
createLoadingView(int position, View convertView, ViewGroup parent, Object lock, LayoutInflater layoutInflater, OnClickHandler handler)469         private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
470                 ViewGroup parent, Object lock, LayoutInflater layoutInflater, OnClickHandler
471                 handler) {
472             // Create and return a new FrameLayout, and setup the references for this position
473             final Context context = parent.getContext();
474             RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
475 
476             // Create a new loading view
477             synchronized (lock) {
478                 boolean customLoadingViewAvailable = false;
479 
480                 if (mUserLoadingView != null) {
481                     // Try to inflate user-specified loading view
482                     try {
483                         View loadingView = mUserLoadingView.apply(parent.getContext(), parent,
484                                 handler);
485                         loadingView.setTagInternal(com.android.internal.R.id.rowTypeId,
486                                 new Integer(0));
487                         layout.addView(loadingView);
488                         customLoadingViewAvailable = true;
489                     } catch (Exception e) {
490                         Log.w(TAG, "Error inflating custom loading view, using default loading" +
491                                 "view instead", e);
492                     }
493                 }
494                 if (!customLoadingViewAvailable) {
495                     // A default loading view
496                     // Use the size of the first row as a guide for the size of the loading view
497                     if (mFirstViewHeight < 0) {
498                         try {
499                             View firstView = mFirstView.apply(parent.getContext(), parent, handler);
500                             firstView.measure(
501                                     MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
502                                     MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
503                             mFirstViewHeight = firstView.getMeasuredHeight();
504                             mFirstView = null;
505                         } catch (Exception e) {
506                             float density = context.getResources().getDisplayMetrics().density;
507                             mFirstViewHeight = (int)
508                                     Math.round(sDefaultLoadingViewHeight * density);
509                             mFirstView = null;
510                             Log.w(TAG, "Error inflating first RemoteViews" + e);
511                         }
512                     }
513 
514                     // Compose the loading view text
515                     TextView loadingTextView = (TextView) layoutInflater.inflate(
516                             com.android.internal.R.layout.remote_views_adapter_default_loading_view,
517                             layout, false);
518                     loadingTextView.setHeight(mFirstViewHeight);
519                     loadingTextView.setTag(new Integer(0));
520 
521                     layout.addView(loadingTextView);
522                 }
523             }
524 
525             return layout;
526         }
527     }
528 
529     /**
530      * The meta-data associated with a single item in the cache.
531      */
532     private static class RemoteViewsIndexMetaData {
533         int typeId;
534         long itemId;
535 
RemoteViewsIndexMetaData(RemoteViews v, long itemId)536         public RemoteViewsIndexMetaData(RemoteViews v, long itemId) {
537             set(v, itemId);
538         }
539 
set(RemoteViews v, long id)540         public void set(RemoteViews v, long id) {
541             itemId = id;
542             if (v != null) {
543                 typeId = v.getLayoutId();
544             } else {
545                 typeId = 0;
546             }
547         }
548     }
549 
550     /**
551      *
552      */
553     private static class FixedSizeRemoteViewsCache {
554         private static final String TAG = "FixedSizeRemoteViewsCache";
555 
556         // The meta data related to all the RemoteViews, ie. count, is stable, etc.
557         // The meta data objects are made final so that they can be locked on independently
558         // of the FixedSizeRemoteViewsCache. If we ever lock on both meta data objects, it is in
559         // the order mTemporaryMetaData followed by mMetaData.
560         private final RemoteViewsMetaData mMetaData;
561         private final RemoteViewsMetaData mTemporaryMetaData;
562 
563         // The cache/mapping of position to RemoteViewsMetaData.  This set is guaranteed to be
564         // greater than or equal to the set of RemoteViews.
565         // Note: The reason that we keep this separate from the RemoteViews cache below is that this
566         // we still need to be able to access the mapping of position to meta data, without keeping
567         // the heavy RemoteViews around.  The RemoteViews cache is trimmed to fixed constraints wrt.
568         // memory and size, but this metadata cache will retain information until the data at the
569         // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
570         private HashMap<Integer, RemoteViewsIndexMetaData> mIndexMetaData;
571 
572         // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
573         // too much memory.
574         private HashMap<Integer, RemoteViews> mIndexRemoteViews;
575 
576         // The set of indices that have been explicitly requested by the collection view
577         private HashSet<Integer> mRequestedIndices;
578 
579         // We keep a reference of the last requested index to determine which item to prune the
580         // farthest items from when we hit the memory limit
581         private int mLastRequestedIndex;
582 
583         // The set of indices to load, including those explicitly requested, as well as those
584         // determined by the preloading algorithm to be prefetched
585         private HashSet<Integer> mLoadIndices;
586 
587         // The lower and upper bounds of the preloaded range
588         private int mPreloadLowerBound;
589         private int mPreloadUpperBound;
590 
591         // The bounds of this fixed cache, we will try and fill as many items into the cache up to
592         // the maxCount number of items, or the maxSize memory usage.
593         // The maxCountSlack is used to determine if a new position in the cache to be loaded is
594         // sufficiently ouside the old set, prompting a shifting of the "window" of items to be
595         // preloaded.
596         private int mMaxCount;
597         private int mMaxCountSlack;
598         private static final float sMaxCountSlackPercent = 0.75f;
599         private static final int sMaxMemoryLimitInBytes = 2 * 1024 * 1024;
600 
FixedSizeRemoteViewsCache(int maxCacheSize)601         public FixedSizeRemoteViewsCache(int maxCacheSize) {
602             mMaxCount = maxCacheSize;
603             mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
604             mPreloadLowerBound = 0;
605             mPreloadUpperBound = -1;
606             mMetaData = new RemoteViewsMetaData();
607             mTemporaryMetaData = new RemoteViewsMetaData();
608             mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
609             mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
610             mRequestedIndices = new HashSet<Integer>();
611             mLastRequestedIndex = -1;
612             mLoadIndices = new HashSet<Integer>();
613         }
614 
insert(int position, RemoteViews v, long itemId, ArrayList<Integer> visibleWindow)615         public void insert(int position, RemoteViews v, long itemId,
616                 ArrayList<Integer> visibleWindow) {
617             // Trim the cache if we go beyond the count
618             if (mIndexRemoteViews.size() >= mMaxCount) {
619                 mIndexRemoteViews.remove(getFarthestPositionFrom(position, visibleWindow));
620             }
621 
622             // Trim the cache if we go beyond the available memory size constraints
623             int pruneFromPosition = (mLastRequestedIndex > -1) ? mLastRequestedIndex : position;
624             while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryLimitInBytes) {
625                 // Note: This is currently the most naive mechanism for deciding what to prune when
626                 // we hit the memory limit.  In the future, we may want to calculate which index to
627                 // remove based on both its position as well as it's current memory usage, as well
628                 // as whether it was directly requested vs. whether it was preloaded by our caching
629                 // mechanism.
630                 mIndexRemoteViews.remove(getFarthestPositionFrom(pruneFromPosition, visibleWindow));
631             }
632 
633             // Update the metadata cache
634             if (mIndexMetaData.containsKey(position)) {
635                 final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
636                 metaData.set(v, itemId);
637             } else {
638                 mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId));
639             }
640             mIndexRemoteViews.put(position, v);
641         }
642 
getMetaData()643         public RemoteViewsMetaData getMetaData() {
644             return mMetaData;
645         }
getTemporaryMetaData()646         public RemoteViewsMetaData getTemporaryMetaData() {
647             return mTemporaryMetaData;
648         }
getRemoteViewsAt(int position)649         public RemoteViews getRemoteViewsAt(int position) {
650             if (mIndexRemoteViews.containsKey(position)) {
651                 return mIndexRemoteViews.get(position);
652             }
653             return null;
654         }
getMetaDataAt(int position)655         public RemoteViewsIndexMetaData getMetaDataAt(int position) {
656             if (mIndexMetaData.containsKey(position)) {
657                 return mIndexMetaData.get(position);
658             }
659             return null;
660         }
661 
commitTemporaryMetaData()662         public void commitTemporaryMetaData() {
663             synchronized (mTemporaryMetaData) {
664                 synchronized (mMetaData) {
665                     mMetaData.set(mTemporaryMetaData);
666                 }
667             }
668         }
669 
getRemoteViewsBitmapMemoryUsage()670         private int getRemoteViewsBitmapMemoryUsage() {
671             // Calculate the memory usage of all the RemoteViews bitmaps being cached
672             int mem = 0;
673             for (Integer i : mIndexRemoteViews.keySet()) {
674                 final RemoteViews v = mIndexRemoteViews.get(i);
675                 if (v != null) {
676                     mem += v.estimateMemoryUsage();
677                 }
678             }
679             return mem;
680         }
681 
getFarthestPositionFrom(int pos, ArrayList<Integer> visibleWindow)682         private int getFarthestPositionFrom(int pos, ArrayList<Integer> visibleWindow) {
683             // Find the index farthest away and remove that
684             int maxDist = 0;
685             int maxDistIndex = -1;
686             int maxDistNotVisible = 0;
687             int maxDistIndexNotVisible = -1;
688             for (int i : mIndexRemoteViews.keySet()) {
689                 int dist = Math.abs(i-pos);
690                 if (dist > maxDistNotVisible && !visibleWindow.contains(i)) {
691                     // maxDistNotVisible/maxDistIndexNotVisible will store the index of the
692                     // farthest non-visible position
693                     maxDistIndexNotVisible = i;
694                     maxDistNotVisible = dist;
695                 }
696                 if (dist >= maxDist) {
697                     // maxDist/maxDistIndex will store the index of the farthest position
698                     // regardless of whether it is visible or not
699                     maxDistIndex = i;
700                     maxDist = dist;
701                 }
702             }
703             if (maxDistIndexNotVisible > -1) {
704                 return maxDistIndexNotVisible;
705             }
706             return maxDistIndex;
707         }
708 
queueRequestedPositionToLoad(int position)709         public void queueRequestedPositionToLoad(int position) {
710             mLastRequestedIndex = position;
711             synchronized (mLoadIndices) {
712                 mRequestedIndices.add(position);
713                 mLoadIndices.add(position);
714             }
715         }
queuePositionsToBePreloadedFromRequestedPosition(int position)716         public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
717             // Check if we need to preload any items
718             if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
719                 int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
720                 if (Math.abs(position - center) < mMaxCountSlack) {
721                     return false;
722                 }
723             }
724 
725             int count = 0;
726             synchronized (mMetaData) {
727                 count = mMetaData.count;
728             }
729             synchronized (mLoadIndices) {
730                 mLoadIndices.clear();
731 
732                 // Add all the requested indices
733                 mLoadIndices.addAll(mRequestedIndices);
734 
735                 // Add all the preload indices
736                 int halfMaxCount = mMaxCount / 2;
737                 mPreloadLowerBound = position - halfMaxCount;
738                 mPreloadUpperBound = position + halfMaxCount;
739                 int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
740                 int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
741                 for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
742                     mLoadIndices.add(i);
743                 }
744 
745                 // But remove all the indices that have already been loaded and are cached
746                 mLoadIndices.removeAll(mIndexRemoteViews.keySet());
747             }
748             return true;
749         }
750         /** Returns the next index to load, and whether that index was directly requested or not */
getNextIndexToLoad()751         public int[] getNextIndexToLoad() {
752             // We try and prioritize items that have been requested directly, instead
753             // of items that are loaded as a result of the caching mechanism
754             synchronized (mLoadIndices) {
755                 // Prioritize requested indices to be loaded first
756                 if (!mRequestedIndices.isEmpty()) {
757                     Integer i = mRequestedIndices.iterator().next();
758                     mRequestedIndices.remove(i);
759                     mLoadIndices.remove(i);
760                     return new int[]{i.intValue(), 1};
761                 }
762 
763                 // Otherwise, preload other indices as necessary
764                 if (!mLoadIndices.isEmpty()) {
765                     Integer i = mLoadIndices.iterator().next();
766                     mLoadIndices.remove(i);
767                     return new int[]{i.intValue(), 0};
768                 }
769 
770                 return new int[]{-1, 0};
771             }
772         }
773 
containsRemoteViewAt(int position)774         public boolean containsRemoteViewAt(int position) {
775             return mIndexRemoteViews.containsKey(position);
776         }
containsMetaDataAt(int position)777         public boolean containsMetaDataAt(int position) {
778             return mIndexMetaData.containsKey(position);
779         }
780 
reset()781         public void reset() {
782             // Note: We do not try and reset the meta data, since that information is still used by
783             // collection views to validate it's own contents (and will be re-requested if the data
784             // is invalidated through the notifyDataSetChanged() flow).
785 
786             mPreloadLowerBound = 0;
787             mPreloadUpperBound = -1;
788             mLastRequestedIndex = -1;
789             mIndexRemoteViews.clear();
790             mIndexMetaData.clear();
791             synchronized (mLoadIndices) {
792                 mRequestedIndices.clear();
793                 mLoadIndices.clear();
794             }
795         }
796     }
797 
798     static class RemoteViewsCacheKey {
799         final Intent.FilterComparison filter;
800         final int widgetId;
801         final int userId;
802 
RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId, int userId)803         RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId, int userId) {
804             this.filter = filter;
805             this.widgetId = widgetId;
806             this.userId = userId;
807         }
808 
809         @Override
equals(Object o)810         public boolean equals(Object o) {
811             if (!(o instanceof RemoteViewsCacheKey)) {
812                 return false;
813             }
814             RemoteViewsCacheKey other = (RemoteViewsCacheKey) o;
815             return other.filter.equals(filter) && other.widgetId == widgetId
816                     && other.userId == userId;
817         }
818 
819         @Override
hashCode()820         public int hashCode() {
821             return (filter == null ? 0 : filter.hashCode()) ^ (widgetId << 2) ^ (userId << 10);
822         }
823     }
824 
RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback)825     public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
826         mContext = context;
827         mIntent = intent;
828         mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
829         mLayoutInflater = LayoutInflater.from(context);
830         if (mIntent == null) {
831             throw new IllegalArgumentException("Non-null Intent must be specified.");
832         }
833         mRequestedViews = new RemoteViewsFrameLayoutRefSet();
834 
835         checkInteractAcrossUsersPermission(context, UserHandle.myUserId());
836         mUserId = context.getUserId();
837 
838         // Strip the previously injected app widget id from service intent
839         if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
840             intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
841         }
842 
843         // Initialize the worker thread
844         mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
845         mWorkerThread.start();
846         mWorkerQueue = new Handler(mWorkerThread.getLooper());
847         mMainQueue = new Handler(Looper.myLooper(), this);
848 
849         if (sCacheRemovalThread == null) {
850             sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
851             sCacheRemovalThread.start();
852             sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
853         }
854 
855         // Initialize the cache and the service connection on startup
856         mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
857         mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
858 
859         RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
860                 mAppWidgetId, mUserId);
861 
862         synchronized(sCachedRemoteViewsCaches) {
863             if (sCachedRemoteViewsCaches.containsKey(key)) {
864                 mCache = sCachedRemoteViewsCaches.get(key);
865                 synchronized (mCache.mMetaData) {
866                     if (mCache.mMetaData.count > 0) {
867                         // As a precautionary measure, we verify that the meta data indicates a
868                         // non-zero count before declaring that data is ready.
869                         mDataReady = true;
870                     }
871                 }
872             } else {
873                 mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
874             }
875             if (!mDataReady) {
876                 requestBindService();
877             }
878         }
879     }
880 
checkInteractAcrossUsersPermission(Context context, int userId)881     private static void checkInteractAcrossUsersPermission(Context context, int userId) {
882         if (context.getUserId() != userId
883                 && context.checkCallingOrSelfPermission(MULTI_USER_PERM)
884                 != PackageManager.PERMISSION_GRANTED) {
885             throw new SecurityException("Must have permission " + MULTI_USER_PERM
886                     + " to inflate another user's widget");
887         }
888     }
889 
890     @Override
finalize()891     protected void finalize() throws Throwable {
892         try {
893             if (mWorkerThread != null) {
894                 mWorkerThread.quit();
895             }
896         } finally {
897             super.finalize();
898         }
899     }
900 
isDataReady()901     public boolean isDataReady() {
902         return mDataReady;
903     }
904 
setRemoteViewsOnClickHandler(OnClickHandler handler)905     public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
906         mRemoteViewsOnClickHandler = handler;
907     }
908 
saveRemoteViewsCache()909     public void saveRemoteViewsCache() {
910         final RemoteViewsCacheKey key = new RemoteViewsCacheKey(
911                 new Intent.FilterComparison(mIntent), mAppWidgetId, mUserId);
912 
913         synchronized(sCachedRemoteViewsCaches) {
914             // If we already have a remove runnable posted for this key, remove it.
915             if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
916                 sCacheRemovalQueue.removeCallbacks(sRemoteViewsCacheRemoveRunnables.get(key));
917                 sRemoteViewsCacheRemoveRunnables.remove(key);
918             }
919 
920             int metaDataCount = 0;
921             int numRemoteViewsCached = 0;
922             synchronized (mCache.mMetaData) {
923                 metaDataCount = mCache.mMetaData.count;
924             }
925             synchronized (mCache) {
926                 numRemoteViewsCached = mCache.mIndexRemoteViews.size();
927             }
928             if (metaDataCount > 0 && numRemoteViewsCached > 0) {
929                 sCachedRemoteViewsCaches.put(key, mCache);
930             }
931 
932             Runnable r = new Runnable() {
933                 @Override
934                 public void run() {
935                     synchronized (sCachedRemoteViewsCaches) {
936                         if (sCachedRemoteViewsCaches.containsKey(key)) {
937                             sCachedRemoteViewsCaches.remove(key);
938                         }
939                         if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
940                             sRemoteViewsCacheRemoveRunnables.remove(key);
941                         }
942                     }
943                 }
944             };
945             sRemoteViewsCacheRemoveRunnables.put(key, r);
946             sCacheRemovalQueue.postDelayed(r, REMOTE_VIEWS_CACHE_DURATION);
947         }
948     }
949 
loadNextIndexInBackground()950     private void loadNextIndexInBackground() {
951         mWorkerQueue.post(new Runnable() {
952             @Override
953             public void run() {
954                 if (mServiceConnection.isConnected()) {
955                     // Get the next index to load
956                     int position = -1;
957                     synchronized (mCache) {
958                         int[] res = mCache.getNextIndexToLoad();
959                         position = res[0];
960                     }
961                     if (position > -1) {
962                         // Load the item, and notify any existing RemoteViewsFrameLayouts
963                         updateRemoteViews(position, true);
964 
965                         // Queue up for the next one to load
966                         loadNextIndexInBackground();
967                     } else {
968                         // No more items to load, so queue unbind
969                         enqueueDeferredUnbindServiceMessage();
970                     }
971                 }
972             }
973         });
974     }
975 
processException(String method, Exception e)976     private void processException(String method, Exception e) {
977         Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
978 
979         // If we encounter a crash when updating, we should reset the metadata & cache and trigger
980         // a notifyDataSetChanged to update the widget accordingly
981         final RemoteViewsMetaData metaData = mCache.getMetaData();
982         synchronized (metaData) {
983             metaData.reset();
984         }
985         synchronized (mCache) {
986             mCache.reset();
987         }
988         mMainQueue.post(new Runnable() {
989             @Override
990             public void run() {
991                 superNotifyDataSetChanged();
992             }
993         });
994     }
995 
updateTemporaryMetaData()996     private void updateTemporaryMetaData() {
997         IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
998 
999         try {
1000             // get the properties/first view (so that we can use it to
1001             // measure our dummy views)
1002             boolean hasStableIds = factory.hasStableIds();
1003             int viewTypeCount = factory.getViewTypeCount();
1004             int count = factory.getCount();
1005             RemoteViews loadingView = factory.getLoadingView();
1006             RemoteViews firstView = null;
1007             if ((count > 0) && (loadingView == null)) {
1008                 firstView = factory.getViewAt(0);
1009             }
1010             final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
1011             synchronized (tmpMetaData) {
1012                 tmpMetaData.hasStableIds = hasStableIds;
1013                 // We +1 because the base view type is the loading view
1014                 tmpMetaData.viewTypeCount = viewTypeCount + 1;
1015                 tmpMetaData.count = count;
1016                 tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
1017             }
1018         } catch(RemoteException e) {
1019             processException("updateMetaData", e);
1020         } catch(RuntimeException e) {
1021             processException("updateMetaData", e);
1022         }
1023     }
1024 
updateRemoteViews(final int position, boolean notifyWhenLoaded)1025     private void updateRemoteViews(final int position, boolean notifyWhenLoaded) {
1026         IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
1027 
1028         // Load the item information from the remote service
1029         RemoteViews remoteViews = null;
1030         long itemId = 0;
1031         try {
1032             remoteViews = factory.getViewAt(position);
1033             remoteViews.setUser(new UserHandle(mUserId));
1034             itemId = factory.getItemId(position);
1035         } catch (RemoteException e) {
1036             Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
1037 
1038             // Return early to prevent additional work in re-centering the view cache, and
1039             // swapping from the loading view
1040             return;
1041         } catch (RuntimeException e) {
1042             Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
1043             return;
1044         }
1045 
1046         if (remoteViews == null) {
1047             // If a null view was returned, we break early to prevent it from getting
1048             // into our cache and causing problems later. The effect is that the child  at this
1049             // position will remain as a loading view until it is updated.
1050             Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
1051                     "returned from RemoteViewsFactory.");
1052             return;
1053         }
1054 
1055         int layoutId = remoteViews.getLayoutId();
1056         RemoteViewsMetaData metaData = mCache.getMetaData();
1057         boolean viewTypeInRange;
1058         int cacheCount;
1059         synchronized (metaData) {
1060             viewTypeInRange = metaData.isViewTypeInRange(layoutId);
1061             cacheCount = mCache.mMetaData.count;
1062         }
1063         synchronized (mCache) {
1064             if (viewTypeInRange) {
1065                 ArrayList<Integer> visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
1066                         mVisibleWindowUpperBound, cacheCount);
1067                 // Cache the RemoteViews we loaded
1068                 mCache.insert(position, remoteViews, itemId, visibleWindow);
1069 
1070                 // Notify all the views that we have previously returned for this index that
1071                 // there is new data for it.
1072                 final RemoteViews rv = remoteViews;
1073                 if (notifyWhenLoaded) {
1074                     mMainQueue.post(new Runnable() {
1075                         @Override
1076                         public void run() {
1077                             mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
1078                         }
1079                     });
1080                 }
1081             } else {
1082                 // We need to log an error here, as the the view type count specified by the
1083                 // factory is less than the number of view types returned. We don't return this
1084                 // view to the AdapterView, as this will cause an exception in the hosting process,
1085                 // which contains the associated AdapterView.
1086                 Log.e(TAG, "Error: widget's RemoteViewsFactory returns more view types than " +
1087                         " indicated by getViewTypeCount() ");
1088             }
1089         }
1090     }
1091 
getRemoteViewsServiceIntent()1092     public Intent getRemoteViewsServiceIntent() {
1093         return mIntent;
1094     }
1095 
getCount()1096     public int getCount() {
1097         final RemoteViewsMetaData metaData = mCache.getMetaData();
1098         synchronized (metaData) {
1099             return metaData.count;
1100         }
1101     }
1102 
getItem(int position)1103     public Object getItem(int position) {
1104         // Disallow arbitrary object to be associated with an item for the time being
1105         return null;
1106     }
1107 
getItemId(int position)1108     public long getItemId(int position) {
1109         synchronized (mCache) {
1110             if (mCache.containsMetaDataAt(position)) {
1111                 return mCache.getMetaDataAt(position).itemId;
1112             }
1113             return 0;
1114         }
1115     }
1116 
getItemViewType(int position)1117     public int getItemViewType(int position) {
1118         int typeId = 0;
1119         synchronized (mCache) {
1120             if (mCache.containsMetaDataAt(position)) {
1121                 typeId = mCache.getMetaDataAt(position).typeId;
1122             } else {
1123                 return 0;
1124             }
1125         }
1126 
1127         final RemoteViewsMetaData metaData = mCache.getMetaData();
1128         synchronized (metaData) {
1129             return metaData.getMappedViewType(typeId);
1130         }
1131     }
1132 
1133     /**
1134      * Returns the item type id for the specified convert view.  Returns -1 if the convert view
1135      * is invalid.
1136      */
getConvertViewTypeId(View convertView)1137     private int getConvertViewTypeId(View convertView) {
1138         int typeId = -1;
1139         if (convertView != null) {
1140             Object tag = convertView.getTag(com.android.internal.R.id.rowTypeId);
1141             if (tag != null) {
1142                 typeId = (Integer) tag;
1143             }
1144         }
1145         return typeId;
1146     }
1147 
1148     /**
1149      * This method allows an AdapterView using this Adapter to provide information about which
1150      * views are currently being displayed. This allows for certain optimizations and preloading
1151      * which  wouldn't otherwise be possible.
1152      */
setVisibleRangeHint(int lowerBound, int upperBound)1153     public void setVisibleRangeHint(int lowerBound, int upperBound) {
1154         mVisibleWindowLowerBound = lowerBound;
1155         mVisibleWindowUpperBound = upperBound;
1156     }
1157 
getView(int position, View convertView, ViewGroup parent)1158     public View getView(int position, View convertView, ViewGroup parent) {
1159         // "Request" an index so that we can queue it for loading, initiate subsequent
1160         // preloading, etc.
1161         synchronized (mCache) {
1162             boolean isInCache = mCache.containsRemoteViewAt(position);
1163             boolean isConnected = mServiceConnection.isConnected();
1164             boolean hasNewItems = false;
1165 
1166             if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
1167                 mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
1168             }
1169 
1170             if (!isInCache && !isConnected) {
1171                 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
1172                 // in turn trigger another request to getView()
1173                 requestBindService();
1174             } else {
1175                 // Queue up other indices to be preloaded based on this position
1176                 hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
1177             }
1178 
1179             if (isInCache) {
1180                 View convertViewChild = null;
1181                 int convertViewTypeId = 0;
1182                 RemoteViewsFrameLayout layout = null;
1183 
1184                 if (convertView instanceof RemoteViewsFrameLayout) {
1185                     layout = (RemoteViewsFrameLayout) convertView;
1186                     convertViewChild = layout.getChildAt(0);
1187                     convertViewTypeId = getConvertViewTypeId(convertViewChild);
1188                 }
1189 
1190                 // Second, we try and retrieve the RemoteViews from the cache, returning a loading
1191                 // view and queueing it to be loaded if it has not already been loaded.
1192                 Context context = parent.getContext();
1193                 RemoteViews rv = mCache.getRemoteViewsAt(position);
1194                 RemoteViewsIndexMetaData indexMetaData = mCache.getMetaDataAt(position);
1195                 int typeId = indexMetaData.typeId;
1196 
1197                 try {
1198                     // Reuse the convert view where possible
1199                     if (layout != null) {
1200                         if (convertViewTypeId == typeId) {
1201                             rv.reapply(context, convertViewChild, mRemoteViewsOnClickHandler);
1202                             return layout;
1203                         }
1204                         layout.removeAllViews();
1205                     } else {
1206                         layout = new RemoteViewsFrameLayout(context);
1207                     }
1208 
1209                     // Otherwise, create a new view to be returned
1210                     View newView = rv.apply(context, parent, mRemoteViewsOnClickHandler);
1211                     newView.setTagInternal(com.android.internal.R.id.rowTypeId,
1212                             new Integer(typeId));
1213                     layout.addView(newView);
1214                     return layout;
1215 
1216                 } catch (Exception e){
1217                     // We have to make sure that we successfully inflated the RemoteViews, if not
1218                     // we return the loading view instead.
1219                     Log.w(TAG, "Error inflating RemoteViews at position: " + position + ", using" +
1220                             "loading view instead" + e);
1221 
1222                     RemoteViewsFrameLayout loadingView = null;
1223                     final RemoteViewsMetaData metaData = mCache.getMetaData();
1224                     synchronized (metaData) {
1225                         loadingView = metaData.createLoadingView(position, convertView, parent,
1226                                 mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
1227                     }
1228                     return loadingView;
1229                 } finally {
1230                     if (hasNewItems) loadNextIndexInBackground();
1231                 }
1232             } else {
1233                 // If the cache does not have the RemoteViews at this position, then create a
1234                 // loading view and queue the actual position to be loaded in the background
1235                 RemoteViewsFrameLayout loadingView = null;
1236                 final RemoteViewsMetaData metaData = mCache.getMetaData();
1237                 synchronized (metaData) {
1238                     loadingView = metaData.createLoadingView(position, convertView, parent,
1239                             mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
1240                 }
1241 
1242                 mRequestedViews.add(position, loadingView);
1243                 mCache.queueRequestedPositionToLoad(position);
1244                 loadNextIndexInBackground();
1245 
1246                 return loadingView;
1247             }
1248         }
1249     }
1250 
getViewTypeCount()1251     public int getViewTypeCount() {
1252         final RemoteViewsMetaData metaData = mCache.getMetaData();
1253         synchronized (metaData) {
1254             return metaData.viewTypeCount;
1255         }
1256     }
1257 
hasStableIds()1258     public boolean hasStableIds() {
1259         final RemoteViewsMetaData metaData = mCache.getMetaData();
1260         synchronized (metaData) {
1261             return metaData.hasStableIds;
1262         }
1263     }
1264 
isEmpty()1265     public boolean isEmpty() {
1266         return getCount() <= 0;
1267     }
1268 
onNotifyDataSetChanged()1269     private void onNotifyDataSetChanged() {
1270         // Complete the actual notifyDataSetChanged() call initiated earlier
1271         IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
1272         try {
1273             factory.onDataSetChanged();
1274         } catch (RemoteException e) {
1275             Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
1276 
1277             // Return early to prevent from further being notified (since nothing has
1278             // changed)
1279             return;
1280         } catch (RuntimeException e) {
1281             Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
1282             return;
1283         }
1284 
1285         // Flush the cache so that we can reload new items from the service
1286         synchronized (mCache) {
1287             mCache.reset();
1288         }
1289 
1290         // Re-request the new metadata (only after the notification to the factory)
1291         updateTemporaryMetaData();
1292         int newCount;
1293         ArrayList<Integer> visibleWindow;
1294         synchronized(mCache.getTemporaryMetaData()) {
1295             newCount = mCache.getTemporaryMetaData().count;
1296             visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
1297                     mVisibleWindowUpperBound, newCount);
1298         }
1299 
1300         // Pre-load (our best guess of) the views which are currently visible in the AdapterView.
1301         // This mitigates flashing and flickering of loading views when a widget notifies that
1302         // its data has changed.
1303         for (int i: visibleWindow) {
1304             // Because temporary meta data is only ever modified from this thread (ie.
1305             // mWorkerThread), it is safe to assume that count is a valid representation.
1306             if (i < newCount) {
1307                 updateRemoteViews(i, false);
1308             }
1309         }
1310 
1311         // Propagate the notification back to the base adapter
1312         mMainQueue.post(new Runnable() {
1313             @Override
1314             public void run() {
1315                 synchronized (mCache) {
1316                     mCache.commitTemporaryMetaData();
1317                 }
1318 
1319                 superNotifyDataSetChanged();
1320                 enqueueDeferredUnbindServiceMessage();
1321             }
1322         });
1323 
1324         // Reset the notify flagflag
1325         mNotifyDataSetChangedAfterOnServiceConnected = false;
1326     }
1327 
getVisibleWindow(int lower, int upper, int count)1328     private ArrayList<Integer> getVisibleWindow(int lower, int upper, int count) {
1329         ArrayList<Integer> window = new ArrayList<Integer>();
1330 
1331         // In the case that the window is invalid or uninitialized, return an empty window.
1332         if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
1333             return window;
1334         }
1335 
1336         if (lower <= upper) {
1337             for (int i = lower;  i <= upper; i++){
1338                 window.add(i);
1339             }
1340         } else {
1341             // If the upper bound is less than the lower bound it means that the visible window
1342             // wraps around.
1343             for (int i = lower; i < count; i++) {
1344                 window.add(i);
1345             }
1346             for (int i = 0; i <= upper; i++) {
1347                 window.add(i);
1348             }
1349         }
1350         return window;
1351     }
1352 
notifyDataSetChanged()1353     public void notifyDataSetChanged() {
1354         // Dequeue any unbind messages
1355         mMainQueue.removeMessages(sUnbindServiceMessageType);
1356 
1357         // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
1358         // connect
1359         if (!mServiceConnection.isConnected()) {
1360             if (mNotifyDataSetChangedAfterOnServiceConnected) {
1361                 return;
1362             }
1363 
1364             mNotifyDataSetChangedAfterOnServiceConnected = true;
1365             requestBindService();
1366             return;
1367         }
1368 
1369         mWorkerQueue.post(new Runnable() {
1370             @Override
1371             public void run() {
1372                 onNotifyDataSetChanged();
1373             }
1374         });
1375     }
1376 
superNotifyDataSetChanged()1377     void superNotifyDataSetChanged() {
1378         super.notifyDataSetChanged();
1379     }
1380 
1381     @Override
handleMessage(Message msg)1382     public boolean handleMessage(Message msg) {
1383         boolean result = false;
1384         switch (msg.what) {
1385         case sUnbindServiceMessageType:
1386             if (mServiceConnection.isConnected()) {
1387                 mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
1388             }
1389             result = true;
1390             break;
1391         default:
1392             break;
1393         }
1394         return result;
1395     }
1396 
enqueueDeferredUnbindServiceMessage()1397     private void enqueueDeferredUnbindServiceMessage() {
1398         // Remove any existing deferred-unbind messages
1399         mMainQueue.removeMessages(sUnbindServiceMessageType);
1400         mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
1401     }
1402 
requestBindService()1403     private boolean requestBindService() {
1404         // Try binding the service (which will start it if it's not already running)
1405         if (!mServiceConnection.isConnected()) {
1406             mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
1407         }
1408 
1409         // Remove any existing deferred-unbind messages
1410         mMainQueue.removeMessages(sUnbindServiceMessageType);
1411         return mServiceConnection.isConnected();
1412     }
1413 }
1414