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.AdapterChildHostView {
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
getErrorView()411         protected View getErrorView() {
412             // Use the default loading view as the error view.
413             return getDefaultView();
414         }
415     }
416 
417     /**
418      * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
419      * adapter that have not yet had their RemoteViews loaded.
420      */
421     private class RemoteViewsFrameLayoutRefSet
422             extends SparseArray<ArrayList<RemoteViewsFrameLayout>> {
423 
424         /**
425          * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
426          */
add(int position, RemoteViewsFrameLayout layout)427         public void add(int position, RemoteViewsFrameLayout layout) {
428             ArrayList<RemoteViewsFrameLayout> refs = get(position);
429 
430             // Create the list if necessary
431             if (refs == null) {
432                 refs = new ArrayList<>();
433                 put(position, refs);
434             }
435 
436             // Add the references to the list
437             layout.cacheIndex = position;
438             refs.add(layout);
439         }
440 
441         /**
442          * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
443          * the associated RemoteViews has loaded.
444          */
notifyOnRemoteViewsLoaded(int position, RemoteViews view)445         public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
446             if (view == null) return;
447 
448             // Remove this set from the original mapping
449             final ArrayList<RemoteViewsFrameLayout> refs = removeReturnOld(position);
450             if (refs != null) {
451                 // Notify all the references for that position of the newly loaded RemoteViews
452                 for (final RemoteViewsFrameLayout ref : refs) {
453                     ref.onRemoteViewsLoaded(view, mRemoteViewsInteractionHandler, true);
454                 }
455             }
456         }
457 
458         /**
459          * We need to remove views from this set if they have been recycled by the AdapterView.
460          */
removeView(RemoteViewsFrameLayout rvfl)461         public void removeView(RemoteViewsFrameLayout rvfl) {
462             if (rvfl.cacheIndex < 0) {
463                 return;
464             }
465             final ArrayList<RemoteViewsFrameLayout> refs = get(rvfl.cacheIndex);
466             if (refs != null) {
467                 refs.remove(rvfl);
468             }
469             rvfl.cacheIndex = -1;
470         }
471     }
472 
473     /**
474      * The meta-data associated with the cache in it's current state.
475      */
476     private static class RemoteViewsMetaData {
477         int count;
478         int viewTypeCount;
479         boolean hasStableIds;
480 
481         // Used to determine how to construct loading views.  If a loading view is not specified
482         // by the user, then we try and load the first view, and use its height as the height for
483         // the default loading view.
484         LoadingViewTemplate loadingTemplate;
485 
486         // A mapping from type id to a set of unique type ids
487         private final SparseIntArray mTypeIdIndexMap = new SparseIntArray();
488 
RemoteViewsMetaData()489         public RemoteViewsMetaData() {
490             reset();
491         }
492 
set(RemoteViewsMetaData d)493         public void set(RemoteViewsMetaData d) {
494             synchronized (d) {
495                 count = d.count;
496                 viewTypeCount = d.viewTypeCount;
497                 hasStableIds = d.hasStableIds;
498                 loadingTemplate = d.loadingTemplate;
499             }
500         }
501 
reset()502         public void reset() {
503             count = 0;
504 
505             // by default there is at least one placeholder view type
506             viewTypeCount = 1;
507             hasStableIds = true;
508             loadingTemplate = null;
509             mTypeIdIndexMap.clear();
510         }
511 
getMappedViewType(int typeId)512         public int getMappedViewType(int typeId) {
513             int mappedTypeId = mTypeIdIndexMap.get(typeId, -1);
514             if (mappedTypeId == -1) {
515                 // We +1 because the loading view always has view type id of 0
516                 mappedTypeId = mTypeIdIndexMap.size() + 1;
517                 mTypeIdIndexMap.put(typeId, mappedTypeId);
518             }
519             return mappedTypeId;
520         }
521 
isViewTypeInRange(int typeId)522         public boolean isViewTypeInRange(int typeId) {
523             int mappedType = getMappedViewType(typeId);
524             return (mappedType < viewTypeCount);
525         }
526 
getLoadingTemplate(Context context)527         public synchronized LoadingViewTemplate getLoadingTemplate(Context context) {
528             if (loadingTemplate == null) {
529                 loadingTemplate = new LoadingViewTemplate(null, context);
530             }
531             return loadingTemplate;
532         }
533     }
534 
535     /**
536      * The meta-data associated with a single item in the cache.
537      */
538     private static class RemoteViewsIndexMetaData {
539         int typeId;
540         long itemId;
541 
RemoteViewsIndexMetaData(RemoteViews v, long itemId)542         public RemoteViewsIndexMetaData(RemoteViews v, long itemId) {
543             set(v, itemId);
544         }
545 
set(RemoteViews v, long id)546         public void set(RemoteViews v, long id) {
547             itemId = id;
548             if (v != null) {
549                 typeId = v.getLayoutId();
550             } else {
551                 typeId = 0;
552             }
553         }
554     }
555 
556     /**
557      * Config diff flags for which the cache should be reset
558      */
559     private static final int CACHE_RESET_CONFIG_FLAGS = ActivityInfo.CONFIG_FONT_SCALE
560             | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_DENSITY
561             | ActivityInfo.CONFIG_ASSETS_PATHS;
562     /**
563      *
564      */
565     private static class FixedSizeRemoteViewsCache {
566 
567         // The meta data related to all the RemoteViews, ie. count, is stable, etc.
568         // The meta data objects are made final so that they can be locked on independently
569         // of the FixedSizeRemoteViewsCache. If we ever lock on both meta data objects, it is in
570         // the order mTemporaryMetaData followed by mMetaData.
571         private final RemoteViewsMetaData mMetaData = new RemoteViewsMetaData();
572         private final RemoteViewsMetaData mTemporaryMetaData = new RemoteViewsMetaData();
573 
574         // The cache/mapping of position to RemoteViewsMetaData.  This set is guaranteed to be
575         // greater than or equal to the set of RemoteViews.
576         // Note: The reason that we keep this separate from the RemoteViews cache below is that this
577         // we still need to be able to access the mapping of position to meta data, without keeping
578         // the heavy RemoteViews around.  The RemoteViews cache is trimmed to fixed constraints wrt.
579         // memory and size, but this metadata cache will retain information until the data at the
580         // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
581         private final SparseArray<RemoteViewsIndexMetaData> mIndexMetaData = new SparseArray<>();
582 
583         // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
584         // too much memory.
585         private final SparseArray<RemoteViews> mIndexRemoteViews = new SparseArray<>();
586 
587         // An array of indices to load, Indices which are explicitly requested are set to true,
588         // and those determined by the preloading algorithm to prefetch are set to false.
589         private final SparseBooleanArray mIndicesToLoad = new SparseBooleanArray();
590 
591         // We keep a reference of the last requested index to determine which item to prune the
592         // farthest items from when we hit the memory limit
593         private int mLastRequestedIndex;
594 
595         // The lower and upper bounds of the preloaded range
596         private int mPreloadLowerBound;
597         private int mPreloadUpperBound;
598 
599         // The bounds of this fixed cache, we will try and fill as many items into the cache up to
600         // the maxCount number of items, or the maxSize memory usage.
601         // The maxCountSlack is used to determine if a new position in the cache to be loaded is
602         // sufficiently ouside the old set, prompting a shifting of the "window" of items to be
603         // preloaded.
604         private final int mMaxCount;
605         private final int mMaxCountSlack;
606         private static final float sMaxCountSlackPercent = 0.75f;
607         private static final int sMaxMemoryLimitInBytes = 2 * 1024 * 1024;
608 
609         // Configuration for which the cache was created
610         private final Configuration mConfiguration;
611 
FixedSizeRemoteViewsCache(int maxCacheSize, Configuration configuration)612         FixedSizeRemoteViewsCache(int maxCacheSize, Configuration configuration) {
613             mMaxCount = maxCacheSize;
614             mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
615             mPreloadLowerBound = 0;
616             mPreloadUpperBound = -1;
617             mLastRequestedIndex = -1;
618 
619             mConfiguration = new Configuration(configuration);
620         }
621 
insert(int position, RemoteViews v, long itemId, int[] visibleWindow)622         public void insert(int position, RemoteViews v, long itemId, int[] visibleWindow) {
623             // Trim the cache if we go beyond the count
624             if (mIndexRemoteViews.size() >= mMaxCount) {
625                 mIndexRemoteViews.remove(getFarthestPositionFrom(position, visibleWindow));
626             }
627 
628             // Trim the cache if we go beyond the available memory size constraints
629             int pruneFromPosition = (mLastRequestedIndex > -1) ? mLastRequestedIndex : position;
630             while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryLimitInBytes) {
631                 // Note: This is currently the most naive mechanism for deciding what to prune when
632                 // we hit the memory limit.  In the future, we may want to calculate which index to
633                 // remove based on both its position as well as it's current memory usage, as well
634                 // as whether it was directly requested vs. whether it was preloaded by our caching
635                 // mechanism.
636                 int trimIndex = getFarthestPositionFrom(pruneFromPosition, visibleWindow);
637 
638                 // Need to check that this is a valid index, to cover the case where you have only
639                 // a single view in the cache, but it's larger than the max memory limit
640                 if (trimIndex < 0) {
641                     break;
642                 }
643 
644                 mIndexRemoteViews.remove(trimIndex);
645             }
646 
647             // Update the metadata cache
648             final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
649             if (metaData != null) {
650                 metaData.set(v, itemId);
651             } else {
652                 mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId));
653             }
654             mIndexRemoteViews.put(position, v);
655         }
656 
getMetaData()657         public RemoteViewsMetaData getMetaData() {
658             return mMetaData;
659         }
getTemporaryMetaData()660         public RemoteViewsMetaData getTemporaryMetaData() {
661             return mTemporaryMetaData;
662         }
getRemoteViewsAt(int position)663         public RemoteViews getRemoteViewsAt(int position) {
664             return mIndexRemoteViews.get(position);
665         }
getMetaDataAt(int position)666         public RemoteViewsIndexMetaData getMetaDataAt(int position) {
667             return mIndexMetaData.get(position);
668         }
669 
commitTemporaryMetaData()670         public void commitTemporaryMetaData() {
671             synchronized (mTemporaryMetaData) {
672                 synchronized (mMetaData) {
673                     mMetaData.set(mTemporaryMetaData);
674                 }
675             }
676         }
677 
getRemoteViewsBitmapMemoryUsage()678         private int getRemoteViewsBitmapMemoryUsage() {
679             // Calculate the memory usage of all the RemoteViews bitmaps being cached
680             int mem = 0;
681             for (int i = mIndexRemoteViews.size() - 1; i >= 0; i--) {
682                 final RemoteViews v = mIndexRemoteViews.valueAt(i);
683                 if (v != null) {
684                     mem += v.estimateMemoryUsage();
685                 }
686             }
687             return mem;
688         }
689 
getFarthestPositionFrom(int pos, int[] visibleWindow)690         private int getFarthestPositionFrom(int pos, int[] visibleWindow) {
691             // Find the index farthest away and remove that
692             int maxDist = 0;
693             int maxDistIndex = -1;
694             int maxDistNotVisible = 0;
695             int maxDistIndexNotVisible = -1;
696             for (int i = mIndexRemoteViews.size() - 1; i >= 0; i--) {
697                 int index = mIndexRemoteViews.keyAt(i);
698                 int dist = Math.abs(index-pos);
699                 if (dist > maxDistNotVisible && Arrays.binarySearch(visibleWindow, index) < 0) {
700                     // maxDistNotVisible/maxDistIndexNotVisible will store the index of the
701                     // farthest non-visible position
702                     maxDistIndexNotVisible = index;
703                     maxDistNotVisible = dist;
704                 }
705                 if (dist >= maxDist) {
706                     // maxDist/maxDistIndex will store the index of the farthest position
707                     // regardless of whether it is visible or not
708                     maxDistIndex = index;
709                     maxDist = dist;
710                 }
711             }
712             if (maxDistIndexNotVisible > -1) {
713                 return maxDistIndexNotVisible;
714             }
715             return maxDistIndex;
716         }
717 
queueRequestedPositionToLoad(int position)718         public void queueRequestedPositionToLoad(int position) {
719             mLastRequestedIndex = position;
720             synchronized (mIndicesToLoad) {
721                 mIndicesToLoad.put(position, true);
722             }
723         }
queuePositionsToBePreloadedFromRequestedPosition(int position)724         public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
725             // Check if we need to preload any items
726             if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
727                 int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
728                 if (Math.abs(position - center) < mMaxCountSlack) {
729                     return false;
730                 }
731             }
732 
733             int count;
734             synchronized (mMetaData) {
735                 count = mMetaData.count;
736             }
737             synchronized (mIndicesToLoad) {
738                 // Remove all indices which have not been previously requested.
739                 for (int i = mIndicesToLoad.size() - 1; i >= 0; i--) {
740                     if (!mIndicesToLoad.valueAt(i)) {
741                         mIndicesToLoad.removeAt(i);
742                     }
743                 }
744 
745                 // Add all the preload indices
746                 int halfMaxCount = mMaxCount / 2;
747                 mPreloadLowerBound = position - halfMaxCount;
748                 mPreloadUpperBound = position + halfMaxCount;
749                 int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
750                 int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
751                 for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
752                     if (mIndexRemoteViews.indexOfKey(i) < 0 && !mIndicesToLoad.get(i)) {
753                         // If the index has not been requested, and has not been loaded.
754                         mIndicesToLoad.put(i, false);
755                     }
756                 }
757             }
758             return true;
759         }
760         /** Returns the next index to load */
getNextIndexToLoad()761         public int getNextIndexToLoad() {
762             // We try and prioritize items that have been requested directly, instead
763             // of items that are loaded as a result of the caching mechanism
764             synchronized (mIndicesToLoad) {
765                 // Prioritize requested indices to be loaded first
766                 int index = mIndicesToLoad.indexOfValue(true);
767                 if (index < 0) {
768                     // Otherwise, preload other indices as necessary
769                     index = mIndicesToLoad.indexOfValue(false);
770                 }
771                 if (index < 0) {
772                     return -1;
773                 } else {
774                     int key = mIndicesToLoad.keyAt(index);
775                     mIndicesToLoad.removeAt(index);
776                     return key;
777                 }
778             }
779         }
780 
containsRemoteViewAt(int position)781         public boolean containsRemoteViewAt(int position) {
782             return mIndexRemoteViews.indexOfKey(position) >= 0;
783         }
containsMetaDataAt(int position)784         public boolean containsMetaDataAt(int position) {
785             return mIndexMetaData.indexOfKey(position) >= 0;
786         }
787 
reset()788         public void reset() {
789             // Note: We do not try and reset the meta data, since that information is still used by
790             // collection views to validate it's own contents (and will be re-requested if the data
791             // is invalidated through the notifyDataSetChanged() flow).
792 
793             mPreloadLowerBound = 0;
794             mPreloadUpperBound = -1;
795             mLastRequestedIndex = -1;
796             mIndexRemoteViews.clear();
797             mIndexMetaData.clear();
798             synchronized (mIndicesToLoad) {
799                 mIndicesToLoad.clear();
800             }
801         }
802     }
803 
804     static class RemoteViewsCacheKey {
805         final Intent.FilterComparison filter;
806         final int widgetId;
807 
RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId)808         RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId) {
809             this.filter = filter;
810             this.widgetId = widgetId;
811         }
812 
813         @Override
equals(@ullable Object o)814         public boolean equals(@Nullable Object o) {
815             if (!(o instanceof RemoteViewsCacheKey)) {
816                 return false;
817             }
818             RemoteViewsCacheKey other = (RemoteViewsCacheKey) o;
819             return other.filter.equals(filter) && other.widgetId == widgetId;
820         }
821 
822         @Override
hashCode()823         public int hashCode() {
824             return (filter == null ? 0 : filter.hashCode()) ^ (widgetId << 2);
825         }
826     }
827 
RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback, boolean useAsyncLoader)828     public RemoteViewsAdapter(Context context, Intent intent,
829             RemoteAdapterConnectionCallback callback, boolean useAsyncLoader) {
830         mContext = context;
831         mIntent = intent;
832 
833         if (mIntent == null) {
834             throw new IllegalArgumentException("Non-null Intent must be specified.");
835         }
836 
837         mAppWidgetId = intent.getIntExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
838         mRequestedViews = new RemoteViewsFrameLayoutRefSet();
839         mOnLightBackground = intent.getBooleanExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND, false);
840 
841         // Strip the previously injected app widget id from service intent
842         intent.removeExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID);
843         intent.removeExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND);
844 
845         // Initialize the worker thread
846         mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
847         mWorkerThread.start();
848         mMainHandler = new Handler(Looper.myLooper(), this);
849         mServiceHandler = new RemoteServiceHandler(mWorkerThread.getLooper(), this,
850                 context.getApplicationContext());
851         mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null;
852         mCallback = callback;
853 
854         if (sCacheRemovalThread == null) {
855             sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
856             sCacheRemovalThread.start();
857             sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
858         }
859 
860         RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
861                 mAppWidgetId);
862 
863         synchronized(sCachedRemoteViewsCaches) {
864             FixedSizeRemoteViewsCache cache = sCachedRemoteViewsCaches.get(key);
865             Configuration config = context.getResources().getConfiguration();
866             if (cache == null
867                     || (cache.mConfiguration.diff(config) & CACHE_RESET_CONFIG_FLAGS) != 0) {
868                 mCache = new FixedSizeRemoteViewsCache(DEFAULT_CACHE_SIZE, config);
869             } else {
870                 mCache = sCachedRemoteViewsCaches.get(key);
871                 synchronized (mCache.mMetaData) {
872                     if (mCache.mMetaData.count > 0) {
873                         // As a precautionary measure, we verify that the meta data indicates a
874                         // non-zero count before declaring that data is ready.
875                         mDataReady = true;
876                     }
877                 }
878             }
879             if (!mDataReady) {
880                 requestBindService();
881             }
882         }
883     }
884 
885     @Override
finalize()886     protected void finalize() throws Throwable {
887         try {
888             mServiceHandler.unbindNow();
889             mWorkerThread.quit();
890         } finally {
891             super.finalize();
892         }
893     }
894 
895     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
isDataReady()896     public boolean isDataReady() {
897         return mDataReady;
898     }
899 
900     /** @hide */
setRemoteViewsInteractionHandler(InteractionHandler handler)901     public void setRemoteViewsInteractionHandler(InteractionHandler handler) {
902         mRemoteViewsInteractionHandler = handler;
903     }
904 
905     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
saveRemoteViewsCache()906     public void saveRemoteViewsCache() {
907         final RemoteViewsCacheKey key = new RemoteViewsCacheKey(
908                 new Intent.FilterComparison(mIntent), mAppWidgetId);
909 
910         synchronized(sCachedRemoteViewsCaches) {
911             // If we already have a remove runnable posted for this key, remove it.
912             if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
913                 sCacheRemovalQueue.removeCallbacks(sRemoteViewsCacheRemoveRunnables.get(key));
914                 sRemoteViewsCacheRemoveRunnables.remove(key);
915             }
916 
917             int metaDataCount = 0;
918             int numRemoteViewsCached = 0;
919             synchronized (mCache.mMetaData) {
920                 metaDataCount = mCache.mMetaData.count;
921             }
922             synchronized (mCache) {
923                 numRemoteViewsCached = mCache.mIndexRemoteViews.size();
924             }
925             if (metaDataCount > 0 && numRemoteViewsCached > 0) {
926                 sCachedRemoteViewsCaches.put(key, mCache);
927             }
928 
929             Runnable r = () -> {
930                 synchronized (sCachedRemoteViewsCaches) {
931                     sCachedRemoteViewsCaches.remove(key);
932                     sRemoteViewsCacheRemoveRunnables.remove(key);
933                 }
934             };
935             sRemoteViewsCacheRemoveRunnables.put(key, r);
936             sCacheRemovalQueue.postDelayed(r, REMOTE_VIEWS_CACHE_DURATION);
937         }
938     }
939 
940     @WorkerThread
updateTemporaryMetaData(IRemoteViewsFactory factory)941     private void updateTemporaryMetaData(IRemoteViewsFactory factory) {
942         try {
943             // get the properties/first view (so that we can use it to
944             // measure our placeholder views)
945             boolean hasStableIds = factory.hasStableIds();
946             int viewTypeCount = factory.getViewTypeCount();
947             int count = factory.getCount();
948             LoadingViewTemplate loadingTemplate =
949                     new LoadingViewTemplate(factory.getLoadingView(), mContext);
950             if ((count > 0) && (loadingTemplate.remoteViews == null)) {
951                 RemoteViews firstView = factory.getViewAt(0);
952                 if (firstView != null) {
953                     loadingTemplate.loadFirstViewHeight(firstView, mContext,
954                             new HandlerThreadExecutor(mWorkerThread));
955                 }
956             }
957             final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
958             synchronized (tmpMetaData) {
959                 tmpMetaData.hasStableIds = hasStableIds;
960                 // We +1 because the base view type is the loading view
961                 tmpMetaData.viewTypeCount = viewTypeCount + 1;
962                 tmpMetaData.count = count;
963                 tmpMetaData.loadingTemplate = loadingTemplate;
964             }
965         } catch (RemoteException | RuntimeException e) {
966             Log.e("RemoteViewsAdapter", "Error in updateMetaData: " + e.getMessage());
967 
968             // If we encounter a crash when updating, we should reset the metadata & cache
969             // and trigger a notifyDataSetChanged to update the widget accordingly
970             synchronized (mCache.getMetaData()) {
971                 mCache.getMetaData().reset();
972             }
973             synchronized (mCache) {
974                 mCache.reset();
975             }
976             mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
977         }
978     }
979 
980     @WorkerThread
updateRemoteViews(IRemoteViewsFactory factory, int position, boolean notifyWhenLoaded)981     private void updateRemoteViews(IRemoteViewsFactory factory, int position,
982             boolean notifyWhenLoaded) {
983         // Load the item information from the remote service
984         final RemoteViews remoteViews;
985         final long itemId;
986         try {
987             remoteViews = factory.getViewAt(position);
988             itemId = factory.getItemId(position);
989 
990             if (remoteViews == null) {
991                 throw new RuntimeException("Null remoteViews");
992             }
993         } catch (RemoteException | RuntimeException e) {
994             Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
995 
996             // Return early to prevent additional work in re-centering the view cache, and
997             // swapping from the loading view
998             return;
999         }
1000 
1001         if (remoteViews.mApplication != null) {
1002             // We keep track of last application info. This helps when all the remoteViews have
1003             // same applicationInfo, which should be the case for a typical adapter. But if every
1004             // view has different application info, there will not be any optimization.
1005             if (mLastRemoteViewAppInfo != null
1006                     && remoteViews.hasSameAppInfo(mLastRemoteViewAppInfo)) {
1007                 // We should probably also update the remoteViews for nested ViewActions.
1008                 // Hopefully, RemoteViews in an adapter would be less complicated.
1009                 remoteViews.mApplication = mLastRemoteViewAppInfo;
1010             } else {
1011                 mLastRemoteViewAppInfo = remoteViews.mApplication;
1012             }
1013         }
1014 
1015         int layoutId = remoteViews.getLayoutId();
1016         RemoteViewsMetaData metaData = mCache.getMetaData();
1017         boolean viewTypeInRange;
1018         int cacheCount;
1019         synchronized (metaData) {
1020             viewTypeInRange = metaData.isViewTypeInRange(layoutId);
1021             cacheCount = mCache.mMetaData.count;
1022         }
1023         synchronized (mCache) {
1024             if (viewTypeInRange) {
1025                 int[] visibleWindow = getVisibleWindow(cacheCount);
1026                 // Cache the RemoteViews we loaded
1027                 mCache.insert(position, remoteViews, itemId, visibleWindow);
1028 
1029                 if (notifyWhenLoaded) {
1030                     // Notify all the views that we have previously returned for this index that
1031                     // there is new data for it.
1032                     Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0,
1033                             remoteViews).sendToTarget();
1034                 }
1035             } else {
1036                 // We need to log an error here, as the the view type count specified by the
1037                 // factory is less than the number of view types returned. We don't return this
1038                 // view to the AdapterView, as this will cause an exception in the hosting process,
1039                 // which contains the associated AdapterView.
1040                 Log.e(TAG, "Error: widget's RemoteViewsFactory returns more view types than " +
1041                         " indicated by getViewTypeCount() ");
1042             }
1043         }
1044     }
1045 
1046     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getRemoteViewsServiceIntent()1047     public Intent getRemoteViewsServiceIntent() {
1048         return mIntent;
1049     }
1050 
getCount()1051     public int getCount() {
1052         final RemoteViewsMetaData metaData = mCache.getMetaData();
1053         synchronized (metaData) {
1054             return metaData.count;
1055         }
1056     }
1057 
getItem(int position)1058     public Object getItem(int position) {
1059         // Disallow arbitrary object to be associated with an item for the time being
1060         return null;
1061     }
1062 
getItemId(int position)1063     public long getItemId(int position) {
1064         synchronized (mCache) {
1065             if (mCache.containsMetaDataAt(position)) {
1066                 return mCache.getMetaDataAt(position).itemId;
1067             }
1068             return 0;
1069         }
1070     }
1071 
getItemViewType(int position)1072     public int getItemViewType(int position) {
1073         final int typeId;
1074         synchronized (mCache) {
1075             if (mCache.containsMetaDataAt(position)) {
1076                 typeId = mCache.getMetaDataAt(position).typeId;
1077             } else {
1078                 return 0;
1079             }
1080         }
1081 
1082         final RemoteViewsMetaData metaData = mCache.getMetaData();
1083         synchronized (metaData) {
1084             return metaData.getMappedViewType(typeId);
1085         }
1086     }
1087 
1088     /**
1089      * This method allows an AdapterView using this Adapter to provide information about which
1090      * views are currently being displayed. This allows for certain optimizations and preloading
1091      * which  wouldn't otherwise be possible.
1092      */
1093     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
setVisibleRangeHint(int lowerBound, int upperBound)1094     public void setVisibleRangeHint(int lowerBound, int upperBound) {
1095         mVisibleWindowLowerBound = lowerBound;
1096         mVisibleWindowUpperBound = upperBound;
1097     }
1098 
getView(int position, View convertView, ViewGroup parent)1099     public View getView(int position, View convertView, ViewGroup parent) {
1100         // "Request" an index so that we can queue it for loading, initiate subsequent
1101         // preloading, etc.
1102         synchronized (mCache) {
1103             RemoteViews rv = mCache.getRemoteViewsAt(position);
1104             boolean isInCache = (rv != null);
1105             boolean hasNewItems = false;
1106 
1107             if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
1108                 mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
1109             }
1110 
1111             if (!isInCache) {
1112                 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
1113                 // in turn trigger another request to getView()
1114                 requestBindService();
1115             } else {
1116                 // Queue up other indices to be preloaded based on this position
1117                 hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
1118             }
1119 
1120             final RemoteViewsFrameLayout layout;
1121             if (convertView instanceof RemoteViewsFrameLayout) {
1122                 layout = (RemoteViewsFrameLayout) convertView;
1123             } else {
1124                 layout = new RemoteViewsFrameLayout(parent.getContext(), mCache);
1125                 layout.setExecutor(mAsyncViewLoadExecutor);
1126                 layout.setOnLightBackground(mOnLightBackground);
1127             }
1128 
1129             if (isInCache) {
1130                 // Apply the view synchronously if possible, to avoid flickering
1131                 layout.onRemoteViewsLoaded(rv, mRemoteViewsInteractionHandler, false);
1132                 if (hasNewItems) {
1133                     mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
1134                 }
1135             } else {
1136                 // If the views is not loaded, apply the loading view. If the loading view doesn't
1137                 // exist, the layout will create a default view based on the firstView height.
1138                 layout.onRemoteViewsLoaded(
1139                         mCache.getMetaData().getLoadingTemplate(mContext).remoteViews,
1140                         mRemoteViewsInteractionHandler,
1141                         false);
1142                 mRequestedViews.add(position, layout);
1143                 mCache.queueRequestedPositionToLoad(position);
1144                 mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
1145             }
1146             return layout;
1147         }
1148     }
1149 
getViewTypeCount()1150     public int getViewTypeCount() {
1151         final RemoteViewsMetaData metaData = mCache.getMetaData();
1152         synchronized (metaData) {
1153             return metaData.viewTypeCount;
1154         }
1155     }
1156 
hasStableIds()1157     public boolean hasStableIds() {
1158         final RemoteViewsMetaData metaData = mCache.getMetaData();
1159         synchronized (metaData) {
1160             return metaData.hasStableIds;
1161         }
1162     }
1163 
isEmpty()1164     public boolean isEmpty() {
1165         return getCount() <= 0;
1166     }
1167 
1168     /**
1169      * Returns a sorted array of all integers between lower and upper.
1170      */
getVisibleWindow(int count)1171     private int[] getVisibleWindow(int count) {
1172         int lower = mVisibleWindowLowerBound;
1173         int upper = mVisibleWindowUpperBound;
1174         // In the case that the window is invalid or uninitialized, return an empty window.
1175         if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
1176             return new int[0];
1177         }
1178 
1179         int[] window;
1180         if (lower <= upper) {
1181             window = new int[upper + 1 - lower];
1182             for (int i = lower, j = 0;  i <= upper; i++, j++){
1183                 window[j] = i;
1184             }
1185         } else {
1186             // If the upper bound is less than the lower bound it means that the visible window
1187             // wraps around.
1188             count = Math.max(count, lower);
1189             window = new int[count - lower + upper + 1];
1190             int j = 0;
1191             // Add the entries in sorted order
1192             for (int i = 0; i <= upper; i++, j++) {
1193                 window[j] = i;
1194             }
1195             for (int i = lower; i < count; i++, j++) {
1196                 window[j] = i;
1197             }
1198         }
1199         return window;
1200     }
1201 
notifyDataSetChanged()1202     public void notifyDataSetChanged() {
1203         mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
1204         mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED);
1205     }
1206 
superNotifyDataSetChanged()1207     void superNotifyDataSetChanged() {
1208         super.notifyDataSetChanged();
1209     }
1210 
1211     @Override
handleMessage(Message msg)1212     public boolean handleMessage(Message msg) {
1213         switch (msg.what) {
1214             case MSG_MAIN_HANDLER_COMMIT_METADATA: {
1215                 mCache.commitTemporaryMetaData();
1216                 return true;
1217             }
1218             case MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED: {
1219                 superNotifyDataSetChanged();
1220                 return true;
1221             }
1222             case MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED: {
1223                 if (mCallback != null) {
1224                     mCallback.onRemoteAdapterConnected();
1225                 }
1226                 return true;
1227             }
1228             case MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED: {
1229                 if (mCallback != null) {
1230                     mCallback.onRemoteAdapterDisconnected();
1231                 }
1232                 return true;
1233             }
1234             case MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED: {
1235                 mRequestedViews.notifyOnRemoteViewsLoaded(msg.arg1, (RemoteViews) msg.obj);
1236                 return true;
1237             }
1238         }
1239         return false;
1240     }
1241 
requestBindService()1242     private void requestBindService() {
1243         mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
1244         Message.obtain(mServiceHandler, MSG_REQUEST_BIND, mAppWidgetId, 0, mIntent).sendToTarget();
1245     }
1246 
1247     private static class HandlerThreadExecutor implements Executor {
1248         private final HandlerThread mThread;
1249 
HandlerThreadExecutor(HandlerThread thread)1250         HandlerThreadExecutor(HandlerThread thread) {
1251             mThread = thread;
1252         }
1253 
1254         @Override
execute(Runnable runnable)1255         public void execute(Runnable runnable) {
1256             if (Thread.currentThread().getId() == mThread.getId()) {
1257                 runnable.run();
1258             } else {
1259                 new Handler(mThread.getLooper()).post(runnable);
1260             }
1261         }
1262     }
1263 
1264     private static class LoadingViewTemplate {
1265         public final RemoteViews remoteViews;
1266         public int defaultHeight;
1267 
LoadingViewTemplate(RemoteViews views, Context context)1268         LoadingViewTemplate(RemoteViews views, Context context) {
1269             remoteViews = views;
1270 
1271             float density = context.getResources().getDisplayMetrics().density;
1272             defaultHeight = Math.round(DEFAULT_LOADING_VIEW_HEIGHT * density);
1273         }
1274 
loadFirstViewHeight( RemoteViews firstView, Context context, Executor executor)1275         public void loadFirstViewHeight(
1276                 RemoteViews firstView, Context context, Executor executor) {
1277             // Inflate the first view on the worker thread
1278             firstView.applyAsync(context, new RemoteViewsFrameLayout(context, null), executor,
1279                     new RemoteViews.OnViewAppliedListener() {
1280                         @Override
1281                         public void onViewApplied(View v) {
1282                             try {
1283                                 v.measure(
1284                                         MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
1285                                         MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
1286                                 defaultHeight = v.getMeasuredHeight();
1287                             } catch (Exception e) {
1288                                 onError(e);
1289                             }
1290                         }
1291 
1292                         @Override
1293                         public void onError(Exception e) {
1294                             // Do nothing. The default height will stay the same.
1295                             Log.w(TAG, "Error inflating first RemoteViews", e);
1296                         }
1297                     });
1298         }
1299     }
1300 }
1301