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