Fixing issue where RemoteViewsService.onDestroy() was never being called.

Change-Id: I15ad04eee18bc3e09d4846c7f2f2d2d8d168e518
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index ad25657..62b26c4 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -22,7 +22,6 @@
 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;
@@ -57,7 +56,6 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
@@ -69,11 +67,13 @@
 import android.util.TypedValue;
 import android.util.Xml;
 import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
 
 import com.android.internal.appwidget.IAppWidgetHost;
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.widget.IRemoteViewsAdapterConnection;
+import com.android.internal.widget.IRemoteViewsFactory;
 
 class AppWidgetService extends IAppWidgetService.Stub
 {
@@ -153,9 +153,12 @@
         }
     }
 
-    // Manages connections to RemoteViewsServices
+    // Manages active connections to RemoteViewsServices
     private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
         mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>();
+    // Manages persistent references to RemoteViewsServices from different App Widgets
+    private final HashMap<FilterComparison, HashSet<Integer>>
+        mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>();
 
     Context mContext;
     Locale mLocale;
@@ -429,6 +432,7 @@
         }
     }
 
+    // Binds to a specific RemoteViewsService
     public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
         synchronized (mAppWidgetIds) {
             AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
@@ -452,8 +456,8 @@
             // 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));
+            FilterComparison fc = new FilterComparison(intent);
+            Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc);
             if (mBoundRemoteViewsServices.containsKey(key)) {
                 conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
                 conn.disconnect();
@@ -471,9 +475,15 @@
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
+
+            // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine
+            // when we can call back to the RemoteViewsService later to destroy associated
+            // factories.
+             WidgetRemoteViewsServiceBinding(appWidgetId, fc);
         }
     }
 
+    // Unbinds from a specific RemoteViewsService
     public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
         synchronized (mAppWidgetIds) {
             // Unbind from the RemoteViewsService (which will trigger a callback to the bound
@@ -500,6 +510,7 @@
         }
     }
 
+    // Unbinds from a RemoteViewsService when we delete an app widget
     private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
         int appWidgetId = id.appWidgetId;
         // Unbind all connections to Services bound to this AppWidgetId
@@ -515,6 +526,71 @@
                 it.remove();
             }
         }
+
+        // Check if we need to destroy any services (if no other app widgets are
+        // referencing the same service)
+        decrementAppWidgetServiceRefCount(appWidgetId);
+    }
+
+    // Destroys the cached factory on the RemoteViewsService's side related to the specified intent
+    private void destroyRemoteViewsService(final Intent intent) {
+        final ServiceConnection conn = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                final IRemoteViewsFactory cb =
+                    IRemoteViewsFactory.Stub.asInterface(service);
+                try {
+                    cb.onDestroy(intent);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+                mContext.unbindService(this);
+            }
+            @Override
+            public void onServiceDisconnected(android.content.ComponentName name) {
+                // Do nothing
+            }
+        };
+
+        // Bind to the service and remove the static intent->factory mapping in the
+        // RemoteViewsService.
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Adds to the ref-count for a given RemoteViewsService intent
+    private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) {
+        HashSet<Integer> appWidgetIds = null;
+        if (mRemoteViewsServicesAppWidgets.containsKey(fc)) {
+            appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc);
+        } else {
+            appWidgetIds = new HashSet<Integer>();
+            mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds);
+        }
+        appWidgetIds.add(appWidgetId);
+    }
+
+    // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if
+    // the ref-count reaches zero.
+    private void decrementAppWidgetServiceRefCount(int appWidgetId) {
+        Iterator<FilterComparison> it =
+            mRemoteViewsServicesAppWidgets.keySet().iterator();
+        while (it.hasNext()) {
+            final FilterComparison key = it.next();
+            final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key);
+            if (ids.remove(appWidgetId)) {
+                // If we have removed the last app widget referencing this service, then we
+                // should destroy it and remove it from this set
+                if (ids.isEmpty()) {
+                    destroyRemoteViewsService(key.getIntent());
+                    it.remove();
+                }
+            }
+        }
     }
 
     public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {