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