Re-enabling queued unbinding of services after requests to the service. (3394210)

- Fix for crash when detaching from window
- Potential fix for occasional IllegalStateException when updating List based widgets

Change-Id: I3d3f2bb691552a1136111043db686c4926b510c6
diff --git a/api/11.xml b/api/11.xml
index d17325f..2180aea 100644
--- a/api/11.xml
+++ b/api/11.xml
@@ -238822,7 +238822,7 @@
 >
 </method>
 <method name="onRemoteAdapterConnected"
- return="void"
+ return="boolean"
  abstract="false"
  native="false"
  synchronized="false"
@@ -240800,7 +240800,7 @@
 >
 </method>
 <method name="onRemoteAdapterConnected"
- return="void"
+ return="boolean"
  abstract="false"
  native="false"
  synchronized="false"
@@ -260418,7 +260418,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="arg0" type="T">
+<parameter name="t" type="T">
 </parameter>
 </method>
 </interface>
diff --git a/api/current.xml b/api/current.xml
index b7d7b3a..e34bc8a 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -238833,7 +238833,7 @@
 >
 </method>
 <method name="onRemoteAdapterConnected"
- return="void"
+ return="boolean"
  abstract="false"
  native="false"
  synchronized="false"
@@ -240811,7 +240811,7 @@
 >
 </method>
 <method name="onRemoteAdapterConnected"
- return="void"
+ return="boolean"
  abstract="false"
  native="false"
  synchronized="false"
@@ -260429,7 +260429,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="arg0" type="T">
+<parameter name="t" type="T">
 </parameter>
 </method>
 </interface>
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6d66bbc..631ba0c 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -5289,12 +5289,15 @@
     /**
      * Called back when the adapter connects to the RemoteViewsService.
      */
-    public void onRemoteAdapterConnected() {
+    public boolean onRemoteAdapterConnected() {
         if (mRemoteAdapter != mAdapter) {
             setAdapter(mRemoteAdapter);
+            return false;
         } else if (mRemoteAdapter != null) {
             mRemoteAdapter.superNotifyDataSetChanged();
+            return true;
         }
+        return false;
     }
 
     /**
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index e34a204..23a1a5c 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -54,6 +54,12 @@
     int mWhichChild = 0;
 
     /**
+     * The index of the child to restore after the asynchronous connection from the
+     * RemoteViewsAdapter has been.
+     */
+    private int mRestoreWhichChild = -1;
+
+    /**
      * Whether or not the first view(s) should be animated in
      */
     boolean mAnimateFirstTime = true;
@@ -780,7 +786,15 @@
         // set mWhichChild
         mWhichChild = ss.whichChild;
 
-        setDisplayedChild(mWhichChild);
+        // When using RemoteAdapters, the async connection process can lead to
+        // onRestoreInstanceState to be called before setAdapter(), so we need to save the previous
+        // values to restore the list position after we connect, and can skip setting the displayed
+        // child until then.
+        if (mRemoteViewsAdapter != null && mAdapter == null) {
+            mRestoreWhichChild = mWhichChild;
+        } else {
+            setDisplayedChild(mWhichChild);
+        }
     }
 
     /**
@@ -956,12 +970,21 @@
     /**
      * Called back when the adapter connects to the RemoteViewsService.
      */
-    public void onRemoteAdapterConnected() {
+    public boolean onRemoteAdapterConnected() {
         if (mRemoteViewsAdapter != mAdapter) {
             setAdapter(mRemoteViewsAdapter);
+
+            // Restore the previous position (see onRestoreInstanceState)
+            if (mRestoreWhichChild > -1) {
+                setDisplayedChild(mRestoreWhichChild);
+                mRestoreWhichChild = -1;
+            }
+            return false;
         } else if (mRemoteViewsAdapter != null) {
             mRemoteViewsAdapter.superNotifyDataSetChanged();
+            return true;
         }
+        return false;
     }
 
     /**
@@ -994,7 +1017,7 @@
 
     @Override
     protected void onDetachedFromWindow() {
-        mAdapter = null;
+        setAdapter(null);
         super.onDetachedFromWindow();
     }
 }
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index f329a3e..2457f7f 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -48,11 +48,11 @@
     private static final String TAG = "RemoteViewsAdapter";
 
     // The max number of items in the cache
-    private static final int sDefaultCacheSize = 36;
+    private static final int sDefaultCacheSize = 50;
     // The delay (in millis) to wait until attempting to unbind from a service after a request.
     // This ensures that we don't stay continually bound to the service and that it can be destroyed
     // if we need the memory elsewhere in the system.
-    private static final int sUnbindServiceDelay = 5000;
+    private static final int sUnbindServiceDelay = 1000;
     // Type defs for controlling different messages across the main and worker message queues
     private static final int sDefaultMessageType = 0;
     private static final int sUnbindServiceMessageType = 1;
@@ -65,6 +65,9 @@
     private WeakReference<RemoteAdapterConnectionCallback> mCallback;
     private FixedSizeRemoteViewsCache mCache;
 
+    // A flag to determine whether we should notify data set changed after we connect
+    private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
+
     // The set of requested views that are to be notified when the associated RemoteViews are
     // loaded.
     private RemoteViewsFrameLayoutRefSet mRequestedViews;
@@ -79,7 +82,10 @@
      * are actually connected to/disconnected from their actual services.
      */
     public interface RemoteAdapterConnectionCallback {
-        public void onRemoteAdapterConnected();
+        /**
+         * @return whether the adapter was set or not.
+         */
+        public boolean onRemoteAdapterConnected();
 
         public void onRemoteAdapterDisconnected();
     }
@@ -93,7 +99,8 @@
      */
     private static class RemoteViewsAdapterServiceConnection extends
             IRemoteViewsAdapterConnection.Stub {
-        private boolean mConnected;
+        private boolean mIsConnected;
+        private boolean mIsConnecting;
         private WeakReference<RemoteViewsAdapter> mAdapter;
         private IRemoteViewsFactory mRemoteViewsFactory;
 
@@ -101,27 +108,58 @@
             mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
         }
 
-        public void onServiceConnected(IBinder service) {
-            mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
-            mConnected = true;
+        public synchronized void bind(Context context, int appWidgetId, Intent intent) {
+            if (!mIsConnecting) {
+                try {
+                    final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
+                    mgr.bindRemoteViewsService(appWidgetId, intent, asBinder());
+                    mIsConnecting = true;
+                } catch (Exception e) {
+                    Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
+                    mIsConnecting = false;
+                    mIsConnected = false;
+                }
+            }
+        }
 
-            // Queue up work that we need to do for the callback to run
+        public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
+            try {
+                final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
+                mgr.unbindRemoteViewsService(appWidgetId, intent);
+                mIsConnecting = false;
+            } catch (Exception e) {
+                Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
+                mIsConnecting = false;
+                mIsConnected = false;
+            }
+        }
+
+        public synchronized void onServiceConnected(IBinder service) {
+            mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+
+            // Remove any deferred unbind messages
             final RemoteViewsAdapter adapter = mAdapter.get();
             if (adapter == null) return;
+
+            // Queue up work that we need to do for the callback to run
             adapter.mWorkerQueue.post(new Runnable() {
                 @Override
                 public void run() {
-                    // Call back to the service to notify that the data set changed
-                    if (adapter.mServiceConnection.isConnected()) {
+                    if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
+                        // Handle queued notifyDataSetChanged() if necessary
+                        adapter.onNotifyDataSetChanged();
+                    } else {
                         IRemoteViewsFactory factory =
                             adapter.mServiceConnection.getRemoteViewsFactory();
                         try {
-                            // call back to the factory
-                            factory.onDataSetChanged();
+                            if (!factory.isCreated()) {
+                                // We only call onDataSetChanged() if this is the factory was just
+                                // create in response to this bind
+                                factory.onDataSetChanged();
+                            }
                         } catch (Exception e) {
                             Log.e(TAG, "Error notifying factory of data set changed in " +
                                         "onServiceConnected(): " + e.getMessage());
-                            e.printStackTrace();
 
                             // Return early to prevent anything further from being notified
                             // (effectively nothing has changed)
@@ -130,13 +168,16 @@
 
                         // Request meta data so that we have up to date data when calling back to
                         // the remote adapter callback
-                        adapter.updateMetaData();
+                        adapter.updateTemporaryMetaData();
 
-                        // Post a runnable to call back to the view to notify it that we have
-                        // connected
+                        // Notify the host that we've connected
                         adapter.mMainQueue.post(new Runnable() {
                             @Override
                             public void run() {
+                                synchronized (adapter.mCache) {
+                                    adapter.mCache.commitTemporaryMetaData();
+                                }
+
                                 final RemoteAdapterConnectionCallback callback =
                                     adapter.mCallback.get();
                                 if (callback != null) {
@@ -145,35 +186,44 @@
                             }
                         });
                     }
+
+                    // Enqueue unbind message
+                    adapter.enqueueDeferredUnbindServiceMessage();
+                    mIsConnected = true;
+                    mIsConnecting = false;
                 }
             });
         }
 
-        public void onServiceDisconnected() {
-            mConnected = false;
+        public synchronized void onServiceDisconnected() {
+            mIsConnected = false;
+            mIsConnecting = false;
             mRemoteViewsFactory = null;
 
+            // Clear the main/worker queues
             final RemoteViewsAdapter adapter = mAdapter.get();
             if (adapter == null) return;
             
-            // Clear the main/worker queues
-            adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
-            adapter.mMainQueue.removeMessages(sDefaultMessageType);
-            adapter.mWorkerQueue.removeMessages(sDefaultMessageType);
+            adapter.mMainQueue.post(new Runnable() {
+                @Override
+                public void run() {
+                    // Dequeue any unbind messages
+                    adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
 
-            final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
-            if (callback != null) {
-                callback.onRemoteAdapterDisconnected();
-            }
-            adapter.mCache.reset();
+                    final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
+                    if (callback != null) {
+                        callback.onRemoteAdapterDisconnected();
+                    }
+                }
+            });
         }
 
-        public IRemoteViewsFactory getRemoteViewsFactory() {
+        public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
             return mRemoteViewsFactory;
         }
 
-        public boolean isConnected() {
-            return mConnected;
+        public synchronized boolean isConnected() {
+            return mIsConnected;
         }
     }
 
@@ -270,7 +320,6 @@
         int count;
         int viewTypeCount;
         boolean hasStableIds;
-        boolean isDataDirty;
 
         // Used to determine how to construct loading views.  If a loading view is not specified
         // by the user, then we try and load the first view, and use its height as the height for
@@ -280,22 +329,31 @@
         int mFirstViewHeight;
 
         // A mapping from type id to a set of unique type ids
-        private Map<Integer, Integer> mTypeIdIndexMap;
+        private final HashMap<Integer, Integer> mTypeIdIndexMap = new HashMap<Integer, Integer>();
 
         public RemoteViewsMetaData() {
             reset();
         }
 
+        public void set(RemoteViewsMetaData d) {
+            synchronized (d) {
+                count = d.count;
+                viewTypeCount = d.viewTypeCount;
+                hasStableIds = d.hasStableIds;
+                setLoadingViewTemplates(d.mUserLoadingView, d.mFirstView);
+            }
+        }
+
         public void reset() {
             count = 0;
+
             // by default there is at least one dummy view type
             viewTypeCount = 1;
             hasStableIds = true;
-            isDataDirty = false;
             mUserLoadingView = null;
             mFirstView = null;
             mFirstViewHeight = 0;
-            mTypeIdIndexMap = new HashMap<Integer, Integer>();
+            mTypeIdIndexMap.clear();
         }
 
         public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
@@ -385,6 +443,7 @@
 
         // The meta data related to all the RemoteViews, ie. count, is stable, etc.
         private RemoteViewsMetaData mMetaData;
+        private RemoteViewsMetaData mTemporaryMetaData;
 
         // The cache/mapping of position to RemoteViewsMetaData.  This set is guaranteed to be
         // greater than or equal to the set of RemoteViews.
@@ -426,6 +485,7 @@
             mPreloadLowerBound = 0;
             mPreloadUpperBound = -1;
             mMetaData = new RemoteViewsMetaData();
+            mTemporaryMetaData = new RemoteViewsMetaData();
             mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
             mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
             mRequestedIndices = new HashSet<Integer>();
@@ -461,6 +521,9 @@
         public RemoteViewsMetaData getMetaData() {
             return mMetaData;
         }
+        public RemoteViewsMetaData getTemporaryMetaData() {
+            return mTemporaryMetaData;
+        }
         public RemoteViews getRemoteViewsAt(int position) {
             if (mIndexRemoteViews.containsKey(position)) {
                 return mIndexRemoteViews.get(position);
@@ -474,6 +537,14 @@
             return null;
         }
 
+        public void commitTemporaryMetaData() {
+            synchronized (mTemporaryMetaData) {
+                synchronized (mMetaData) {
+                    mMetaData.set(mTemporaryMetaData);
+                }
+            }
+        }
+
         private int getRemoteViewsBitmapMemoryUsage() {
             // Calculate the memory usage of all the RemoteViews bitmaps being cached
             int mem = 0;
@@ -505,12 +576,12 @@
                 mLoadIndices.add(position);
             }
         }
-        public void queuePositionsToBePreloadedFromRequestedPosition(int position) {
+        public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
             // Check if we need to preload any items
             if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
                 int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
                 if (Math.abs(position - center) < mMaxCountSlack) {
-                    return;
+                    return false;
                 }
             }
 
@@ -537,6 +608,7 @@
                 // But remove all the indices that have already been loaded and are cached
                 mLoadIndices.removeAll(mIndexRemoteViews.keySet());
             }
+            return true;
         }
         public int getNextIndexToLoad() {
             // We try and prioritize items that have been requested directly, instead
@@ -616,100 +688,114 @@
         mWorkerQueue.post(new Runnable() {
             @Override
             public void run() {
-                // Get the next index to load
-                int position = -1;
-                synchronized (mCache) {
-                    position = mCache.getNextIndexToLoad();
-                }
-                if (position > -1) {
-                    // Load the item, and notify any existing RemoteViewsFrameLayouts
-                    updateRemoteViews(position);
+                if (mServiceConnection.isConnected()) {
+                    // Get the next index to load
+                    int position = -1;
+                    synchronized (mCache) {
+                        position = mCache.getNextIndexToLoad();
+                    }
+                    if (position > -1) {
+                        // Load the item, and notify any existing RemoteViewsFrameLayouts
+                        updateRemoteViews(position);
 
-                    // Queue up for the next one to load
-                    loadNextIndexInBackground();
+                        // Queue up for the next one to load
+                        loadNextIndexInBackground();
+                    } else {
+                        // No more items to load, so queue unbind
+                        enqueueDeferredUnbindServiceMessage();
+                    }
                 }
             }
         });
     }
 
-    private void updateMetaData() {
-        if (mServiceConnection.isConnected()) {
-            try {
-                IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+    private void processException(String method, Exception e) {
+        Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
 
-                // get the properties/first view (so that we can use it to
-                // measure our dummy views)
-                boolean hasStableIds = factory.hasStableIds();
-                int viewTypeCount = factory.getViewTypeCount();
-                int count = factory.getCount();
-                RemoteViews loadingView = factory.getLoadingView();
-                RemoteViews firstView = null;
-                if ((count > 0) && (loadingView == null)) {
-                    firstView = factory.getViewAt(0);
-                }
-                final RemoteViewsMetaData metaData = mCache.getMetaData();
-                synchronized (metaData) {
-                    metaData.hasStableIds = hasStableIds;
-                    metaData.viewTypeCount = viewTypeCount + 1;
-                    metaData.count = count;
-                    metaData.setLoadingViewTemplates(loadingView, firstView);
-                }
-            } catch (Exception e) {
-                // print the error
-                Log.e(TAG, "Error in requestMetaData(): " + e.getMessage());
-
-                // reset any members after the failed call
-                final RemoteViewsMetaData metaData = mCache.getMetaData();
-                synchronized (metaData) {
-                    metaData.reset();
-                }
+        // If we encounter a crash when updating, we should reset the metadata & cache and trigger
+        // a notifyDataSetChanged to update the widget accordingly
+        final RemoteViewsMetaData metaData = mCache.getMetaData();
+        synchronized (metaData) {
+            metaData.reset();
+        }
+        synchronized (mCache) {
+            mCache.reset();
+        }
+        mMainQueue.post(new Runnable() {
+            @Override
+            public void run() {
+                superNotifyDataSetChanged();
             }
+        });
+    }
+
+    private void updateTemporaryMetaData() {
+        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+
+        try {
+            // get the properties/first view (so that we can use it to
+            // measure our dummy views)
+            boolean hasStableIds = factory.hasStableIds();
+            int viewTypeCount = factory.getViewTypeCount();
+            int count = factory.getCount();
+            RemoteViews loadingView = factory.getLoadingView();
+            RemoteViews firstView = null;
+            if ((count > 0) && (loadingView == null)) {
+                firstView = factory.getViewAt(0);
+            }
+            final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
+            synchronized (tmpMetaData) {
+                tmpMetaData.hasStableIds = hasStableIds;
+                // We +1 because the base view type is the loading view
+                tmpMetaData.viewTypeCount = viewTypeCount + 1;
+                tmpMetaData.count = count;
+                tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
+            }
+        } catch (Exception e) {
+            processException("updateMetaData", e);
         }
     }
 
     private void updateRemoteViews(final int position) {
-        if (mServiceConnection.isConnected()) {
-            IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+        if (!mServiceConnection.isConnected()) return;
+        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
 
-            // Load the item information from the remote service
-            RemoteViews remoteViews = null;
-            long itemId = 0;
-            try {
-                remoteViews = factory.getViewAt(position);
-                itemId = factory.getItemId(position);
-            } catch (Throwable t) {
-                Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + t.getMessage());
-                t.printStackTrace();
+        // Load the item information from the remote service
+        RemoteViews remoteViews = null;
+        long itemId = 0;
+        try {
+            remoteViews = factory.getViewAt(position);
+            itemId = factory.getItemId(position);
+        } catch (Exception e) {
+            Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
 
-                // Return early to prevent additional work in re-centering the view cache, and
-                // swapping from the loading view
-                return;
-            }
+            // Return early to prevent additional work in re-centering the view cache, and
+            // swapping from the loading view
+            return;
+        }
 
-            if (remoteViews == null) {
-                // If a null view was returned, we break early to prevent it from getting
-                // into our cache and causing problems later. The effect is that the child  at this
-                // position will remain as a loading view until it is updated.
-                Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
-                        "returned from RemoteViewsFactory.");
-                return;
-            }
-            synchronized (mCache) {
-                // Cache the RemoteViews we loaded
-                mCache.insert(position, remoteViews, itemId);
+        if (remoteViews == null) {
+            // If a null view was returned, we break early to prevent it from getting
+            // into our cache and causing problems later. The effect is that the child  at this
+            // position will remain as a loading view until it is updated.
+            Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
+                    "returned from RemoteViewsFactory.");
+            return;
+        }
+        synchronized (mCache) {
+            // Cache the RemoteViews we loaded
+            mCache.insert(position, remoteViews, itemId);
 
-                // Notify all the views that we have previously returned for this index that
-                // there is new data for it.
-                final RemoteViews rv = remoteViews;
-                final int typeId = mCache.getMetaDataAt(position).typeId;
-                mMainQueue.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
-                        enqueueDeferredUnbindServiceMessage();
-                    }
-                });
-            }
+            // Notify all the views that we have previously returned for this index that
+            // there is new data for it.
+            final RemoteViews rv = remoteViews;
+            final int typeId = mCache.getMetaDataAt(position).typeId;
+            mMainQueue.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
+                }
+            });
         }
     }
 
@@ -718,7 +804,6 @@
     }
 
     public int getCount() {
-        requestBindService();
         final RemoteViewsMetaData metaData = mCache.getMetaData();
         synchronized (metaData) {
             return metaData.count;
@@ -731,7 +816,6 @@
     }
 
     public long getItemId(int position) {
-        requestBindService();
         synchronized (mCache) {
             if (mCache.containsMetaDataAt(position)) {
                 return mCache.getMetaDataAt(position).itemId;
@@ -741,7 +825,6 @@
     }
 
     public int getItemViewType(int position) {
-        requestBindService();
         int typeId = 0;
         synchronized (mCache) {
             if (mCache.containsMetaDataAt(position)) {
@@ -773,13 +856,23 @@
     }
 
     public View getView(int position, View convertView, ViewGroup parent) {
-        requestBindService();
-        if (mServiceConnection.isConnected()) {
-            // "Request" an index so that we can queue it for loading, initiate subsequent
-            // preloading, etc.
-            synchronized (mCache) {
+        // "Request" an index so that we can queue it for loading, initiate subsequent
+        // preloading, etc.
+        synchronized (mCache) {
+            boolean isInCache = mCache.containsRemoteViewAt(position);
+            boolean isConnected = mServiceConnection.isConnected();
+            boolean hasNewItems = false;
+
+            if (!isConnected) {
+                // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
+                // in turn trigger another request to getView()
+                requestBindService();
+            } else {
                 // Queue up other indices to be preloaded based on this position
-                mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
+                hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
+            }
+
+            if (isInCache) {
                 View convertViewChild = null;
                 int convertViewTypeId = 0;
                 RemoteViewsFrameLayout layout = null;
@@ -792,51 +885,47 @@
 
                 // Second, we try and retrieve the RemoteViews from the cache, returning a loading
                 // view and queueing it to be loaded if it has not already been loaded.
-                if (mCache.containsRemoteViewAt(position)) {
-                    Context context = parent.getContext();
-                    RemoteViews rv = mCache.getRemoteViewsAt(position);
-                    int typeId = mCache.getMetaDataAt(position).typeId;
+                Context context = parent.getContext();
+                RemoteViews rv = mCache.getRemoteViewsAt(position);
+                int typeId = mCache.getMetaDataAt(position).typeId;
 
-                    // Reuse the convert view where possible
-                    if (layout != null) {
-                        if (convertViewTypeId == typeId) {
-                            rv.reapply(context, convertViewChild);
-                            return layout;
-                        }
+                // Reuse the convert view where possible
+                if (layout != null) {
+                    if (convertViewTypeId == typeId) {
+                        rv.reapply(context, convertViewChild);
+                        return layout;
                     }
-
-                    // Otherwise, create a new view to be returned
-                    View newView = rv.apply(context, parent);
-                    newView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(typeId));
-                    if (layout != null) {
-                        layout.removeAllViews();
-                    } else {
-                        layout = new RemoteViewsFrameLayout(context);
-                    }
-                    layout.addView(newView);
-                    return layout;
+                    layout.removeAllViews();
                 } else {
-                    // If the cache does not have the RemoteViews at this position, then create a
-                    // loading view and queue the actual position to be loaded in the background
-                    RemoteViewsFrameLayout loadingView = null;
-                    final RemoteViewsMetaData metaData = mCache.getMetaData();
-                    synchronized (metaData) {
-                        loadingView = metaData.createLoadingView(position, convertView, parent);
-                    }
-
-                    mRequestedViews.add(position, loadingView);
-                    mCache.queueRequestedPositionToLoad(position);
-                    loadNextIndexInBackground();
-
-                    return loadingView;
+                    layout = new RemoteViewsFrameLayout(context);
                 }
+
+                // Otherwise, create a new view to be returned
+                View newView = rv.apply(context, parent);
+                newView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(typeId));
+                layout.addView(newView);
+                if (hasNewItems) loadNextIndexInBackground();
+
+                return layout;
+            } else {
+                // If the cache does not have the RemoteViews at this position, then create a
+                // loading view and queue the actual position to be loaded in the background
+                RemoteViewsFrameLayout loadingView = null;
+                final RemoteViewsMetaData metaData = mCache.getMetaData();
+                synchronized (metaData) {
+                    loadingView = metaData.createLoadingView(position, convertView, parent);
+                }
+
+                mRequestedViews.add(position, loadingView);
+                mCache.queueRequestedPositionToLoad(position);
+                loadNextIndexInBackground();
+
+                return loadingView;
             }
         }
-        return new View(parent.getContext());
     }
 
     public int getViewTypeCount() {
-        requestBindService();
         final RemoteViewsMetaData metaData = mCache.getMetaData();
         synchronized (metaData) {
             return metaData.viewTypeCount;
@@ -844,7 +933,6 @@
     }
 
     public boolean hasStableIds() {
-        requestBindService();
         final RemoteViewsMetaData metaData = mCache.getMetaData();
         synchronized (metaData) {
             return metaData.hasStableIds;
@@ -855,44 +943,67 @@
         return getCount() <= 0;
     }
 
-    public void notifyDataSetChanged() {
-        mWorkerQueue.post(new Runnable() {
+
+    private void onNotifyDataSetChanged() {
+        // Complete the actual notifyDataSetChanged() call initiated earlier
+        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+        try {
+            factory.onDataSetChanged();
+        } catch (Exception e) {
+            Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+
+            // Return early to prevent from further being notified (since nothing has
+            // changed)
+            return;
+        }
+
+        // Flush the cache so that we can reload new items from the service
+        synchronized (mCache) {
+            mCache.reset();
+        }
+
+        // Re-request the new metadata (only after the notification to the factory)
+        updateTemporaryMetaData();
+
+        // Propagate the notification back to the base adapter
+        mMainQueue.post(new Runnable() {
             @Override
             public void run() {
-                // Complete the actual notifyDataSetChanged() call initiated earlier
-                if (mServiceConnection.isConnected()) {
-                    IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-                    try {
-                        factory.onDataSetChanged();
-                    } catch (Exception e) {
-                        Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-
-                        // Return early to prevent from further being notified (since nothing has
-                        // changed)
-                        return;
-                    }
-                }
-
-                // Flush the cache so that we can reload new items from the service
                 synchronized (mCache) {
-                    mCache.reset();
+                    mCache.commitTemporaryMetaData();
                 }
 
-                // Re-request the new metadata (only after the notification to the factory)
-                updateMetaData();
-
-                // Propagate the notification back to the base adapter
-                mMainQueue.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        superNotifyDataSetChanged();
-                    }
-                });
+                superNotifyDataSetChanged();
+                enqueueDeferredUnbindServiceMessage();
             }
         });
 
-        // Note: we do not call super.notifyDataSetChanged() until the RemoteViewsFactory has had
-        // a chance to update itself and return new meta data associated with the new data.
+        // Reset the notify flagflag
+        mNotifyDataSetChangedAfterOnServiceConnected = false;
+    }
+
+    public void notifyDataSetChanged() {
+        // Dequeue any unbind messages
+        mMainQueue.removeMessages(sUnbindServiceMessageType);
+
+        // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
+        // connect
+        if (!mServiceConnection.isConnected()) {
+            if (mNotifyDataSetChangedAfterOnServiceConnected) {
+                return;
+            }
+
+            mNotifyDataSetChangedAfterOnServiceConnected = true;
+            requestBindService();
+            return;
+        }
+
+        mWorkerQueue.post(new Runnable() {
+            @Override
+            public void run() {
+                onNotifyDataSetChanged();
+            }
+        });
     }
 
     void superNotifyDataSetChanged() {
@@ -904,9 +1015,8 @@
         boolean result = false;
         switch (msg.what) {
         case sUnbindServiceMessageType:
-            final AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
             if (mServiceConnection.isConnected()) {
-                mgr.unbindRemoteViewsService(mAppWidgetId, mIntent);
+                mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
             }
             result = true;
             break;
@@ -917,20 +1027,19 @@
     }
 
     private void enqueueDeferredUnbindServiceMessage() {
-        /* Temporarily disable delayed service unbinding
         // Remove any existing deferred-unbind messages
         mMainQueue.removeMessages(sUnbindServiceMessageType);
         mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
-        */
     }
 
     private boolean requestBindService() {
         // Try binding the service (which will start it if it's not already running)
         if (!mServiceConnection.isConnected()) {
-            final AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
-            mgr.bindRemoteViewsService(mAppWidgetId, mIntent, mServiceConnection.asBinder());
+            mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
         }
 
+        // Remove any existing deferred-unbind messages
+        mMainQueue.removeMessages(sUnbindServiceMessageType);
         return mServiceConnection.isConnected();
     }
 }
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index e5a3de2..fb2c416 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -17,11 +17,11 @@
 package android.widget;
 
 import java.util.HashMap;
-import java.util.Map;
 
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
+import android.util.Log;
 import android.util.Pair;
 
 import com.android.internal.widget.IRemoteViewsFactory;
@@ -35,8 +35,13 @@
 
     private static final String LOG_TAG = "RemoteViewsService";
 
-    // multimap implementation for reference counting
-    private HashMap<Intent.FilterComparison, Pair<RemoteViewsFactory, Integer>> mRemoteViewFactories;
+    // Used for reference counting of RemoteViewsFactories
+    // Because we are now unbinding when we are not using the Service (to allow them to be
+    // reclaimed), the references to the factories that are created need to be stored and used when
+    // the service is restarted (in response to user input for example).  When the process is
+    // destroyed, so is this static cache of RemoteViewsFactories.
+    private static final HashMap<Intent.FilterComparison, RemoteViewsFactory> mRemoteViewFactories =
+            new HashMap<Intent.FilterComparison, RemoteViewsFactory>();
     private final Object mLock = new Object();
 
     /**
@@ -126,9 +131,13 @@
      * A private proxy class for the private IRemoteViewsFactory interface through the
      * public RemoteViewsFactory interface.
      */
-    private class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
-        public RemoteViewsFactoryAdapter(RemoteViewsFactory factory) {
+    private static class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
+        public RemoteViewsFactoryAdapter(RemoteViewsFactory factory, boolean isCreated) {
             mFactory = factory;
+            mIsCreated = isCreated;
+        }
+        public synchronized boolean isCreated() {
+            return mIsCreated;
         }
         public synchronized void onDataSetChanged() {
             mFactory.onDataSetChanged();
@@ -155,58 +164,28 @@
         }
 
         private RemoteViewsFactory mFactory;
-    }
-
-    public RemoteViewsService() {
-        mRemoteViewFactories =
-                new HashMap<Intent.FilterComparison, Pair<RemoteViewsFactory, Integer>>();
+        private boolean mIsCreated;
     }
 
     @Override
     public IBinder onBind(Intent intent) {
         synchronized (mLock) {
-            // increment the reference count to the particular factory associated with this intent
             Intent.FilterComparison fc = new Intent.FilterComparison(intent);
-            Pair<RemoteViewsFactory, Integer> factoryRef = null;
             RemoteViewsFactory factory = null;
+            boolean isCreated = false;
             if (!mRemoteViewFactories.containsKey(fc)) {
                 factory = onGetViewFactory(intent);
-                factoryRef = new Pair<RemoteViewsFactory, Integer>(factory, 1);
-                mRemoteViewFactories.put(fc, factoryRef);
+                mRemoteViewFactories.put(fc, factory);
                 factory.onCreate();
+                isCreated = false;
             } else {
-                Pair<RemoteViewsFactory, Integer> oldFactoryRef = mRemoteViewFactories.get(fc);
-                factory = oldFactoryRef.first;
-                int newRefCount = oldFactoryRef.second.intValue() + 1;
-                factoryRef = new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
-                mRemoteViewFactories.put(fc, factoryRef);
+                factory = mRemoteViewFactories.get(fc);
+                isCreated = true;
             }
-            return new RemoteViewsFactoryAdapter(factory);
+            return new RemoteViewsFactoryAdapter(factory, isCreated);
         }
     }
 
-    @Override
-    public boolean onUnbind(Intent intent) {
-        synchronized (mLock) {
-            Intent.FilterComparison fc = new Intent.FilterComparison(intent);
-            if (mRemoteViewFactories.containsKey(fc)) {
-                // this alleviates the user's responsibility of having to clear all factories
-                Pair<RemoteViewsFactory, Integer> oldFactoryRef =
-                        mRemoteViewFactories.get(fc);
-                int newRefCount = oldFactoryRef.second.intValue() - 1;
-                if (newRefCount <= 0) {
-                    oldFactoryRef.first.onDestroy();
-                    mRemoteViewFactories.remove(fc);
-                } else {
-                    Pair<RemoteViewsFactory, Integer> factoryRef =
-                            new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
-                    mRemoteViewFactories.put(fc, factoryRef);
-                }
-            }
-        }
-        return super.onUnbind(intent);
-    }
-
     /**
      * To be implemented by the derived service to generate appropriate factories for
      * the data.
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index ae9900c..60eca00 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -27,5 +27,6 @@
     int getViewTypeCount();
     long getItemId(int position);
     boolean hasStableIds();
+    boolean isCreated();
 }
 
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 59a540b..ad25657 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -22,6 +22,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -62,6 +63,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.TypedValue;
@@ -121,18 +123,15 @@
      * globally and may lead us to leak AppWidgetService instances (if there were more than one).
      */
     static class ServiceConnectionProxy implements ServiceConnection {
-        private final AppWidgetService mAppWidgetService;
         private final Pair<Integer, Intent.FilterComparison> mKey;
         private final IBinder mConnectionCb;
 
-        ServiceConnectionProxy(AppWidgetService appWidgetService,
-                Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
-            mAppWidgetService = appWidgetService;
+        ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
             mKey = key;
             mConnectionCb = connectionCb;
         }
         public void onServiceConnected(ComponentName name, IBinder service) {
-            IRemoteViewsAdapterConnection cb =
+            final IRemoteViewsAdapterConnection cb =
                 IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
             try {
                 cb.onServiceConnected(service);
@@ -141,19 +140,13 @@
             }
         }
         public void onServiceDisconnected(ComponentName name) {
-            IRemoteViewsAdapterConnection cb =
+            disconnect();
+        }
+        public void disconnect() {
+            final IRemoteViewsAdapterConnection cb =
                 IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
             try {
                 cb.onServiceDisconnected();
-                mAppWidgetService.mServiceConnectionUpdateHandler.post(new Runnable() {
-                    public void run() {
-                        // We don't want to touch mBoundRemoteViewsServices from any other thread
-                        // so queue this to run on the main thread.
-                        if (mAppWidgetService.mBoundRemoteViewsServices.containsKey(mKey)) {
-                            mAppWidgetService.mBoundRemoteViewsServices.remove(mKey);
-                        }
-                    }
-                });
             } catch (RemoteException e) {
                 e.printStackTrace();
             }
@@ -163,7 +156,6 @@
     // Manages connections to RemoteViewsServices
     private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
         mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>();
-    private final Handler mServiceConnectionUpdateHandler = new Handler();
 
     Context mContext;
     Locale mLocale;
@@ -456,13 +448,24 @@
                 throw new IllegalArgumentException("Unknown component " + componentName);
             }
 
-            // Bind to the RemoteViewsService (which will trigger a callback to the
-            // RemoteViewsAdapter)
+            // If there is already a connection made for this service intent, then disconnect from
+            // that first.  (This does not allow multiple connections to the same service under
+            // the same key)
+            ServiceConnectionProxy conn = null;
             Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
                     new FilterComparison(intent));
-            final ServiceConnection conn = new ServiceConnectionProxy(this, key, connection);
+            if (mBoundRemoteViewsServices.containsKey(key)) {
+                conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
+                conn.disconnect();
+                mContext.unbindService(conn);
+                mBoundRemoteViewsServices.remove(key);
+            }
+
+            // Bind to the RemoteViewsService (which will trigger a callback to the
+            // RemoteViewsAdapter.onServiceConnected())
             final long token = Binder.clearCallingIdentity();
             try {
+                conn = new ServiceConnectionProxy(key, connection);
                 mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
                 mBoundRemoteViewsServices.put(key, conn);
             } finally {
@@ -473,37 +476,43 @@
 
     public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
         synchronized (mAppWidgetIds) {
-            AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
-            if (id == null) {
-                throw new IllegalArgumentException("bad appWidgetId");
-            }
-
             // Unbind from the RemoteViewsService (which will trigger a callback to the bound
             // RemoteViewsAdapter)
             Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
                     new FilterComparison(intent));
             if (mBoundRemoteViewsServices.containsKey(key)) {
-                final ServiceConnection conn = mBoundRemoteViewsServices.get(key);
-                mBoundRemoteViewsServices.remove(key);
-                conn.onServiceDisconnected(null);
+                // We don't need to use the appWidgetId until after we are sure there is something
+                // to unbind.  Note that this may mask certain issues with apps calling unbind()
+                // more than necessary.
+                AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
+                if (id == null) {
+                    throw new IllegalArgumentException("bad appWidgetId");
+                }
+
+                ServiceConnectionProxy conn =
+                    (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
+                conn.disconnect();
                 mContext.unbindService(conn);
+                mBoundRemoteViewsServices.remove(key);
+            } else {
+                Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound");
             }
         }
     }
 
     private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
+        int appWidgetId = id.appWidgetId;
+        // Unbind all connections to Services bound to this AppWidgetId
         Iterator<Pair<Integer, Intent.FilterComparison>> it =
             mBoundRemoteViewsServices.keySet().iterator();
-        int appWidgetId = id.appWidgetId;
-
-        // Unbind all connections to AppWidgets bound to this id
         while (it.hasNext()) {
             final Pair<Integer, Intent.FilterComparison> key = it.next();
             if (key.first.intValue() == appWidgetId) {
-                final ServiceConnection conn = mBoundRemoteViewsServices.get(key);
-                it.remove();
-                conn.onServiceDisconnected(null);
+                final ServiceConnectionProxy conn = (ServiceConnectionProxy)
+                        mBoundRemoteViewsServices.get(key);
+                conn.disconnect();
                 mContext.unbindService(conn);
+                it.remove();
             }
         }
     }