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