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