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