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