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