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