diff options
4 files changed, 100 insertions, 15 deletions
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 13a911b..2d2165b 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -20,7 +20,6 @@ import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; -import java.util.Map; import android.appwidget.AppWidgetManager; import android.content.Context; @@ -33,8 +32,8 @@ import android.os.Message; import android.util.Log; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.view.View.MeasureSpec; +import android.view.ViewGroup; import com.android.internal.widget.IRemoteViewsAdapterConnection; import com.android.internal.widget.IRemoteViewsFactory; diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java index fb2c416..e0b08d4 100644 --- a/core/java/android/widget/RemoteViewsService.java +++ b/core/java/android/widget/RemoteViewsService.java @@ -21,8 +21,6 @@ import java.util.HashMap; 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; @@ -40,9 +38,9 @@ public abstract class RemoteViewsService extends Service { // 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 = + private static final HashMap<Intent.FilterComparison, RemoteViewsFactory> sRemoteViewFactories = new HashMap<Intent.FilterComparison, RemoteViewsFactory>(); - private final Object mLock = new Object(); + private static final Object sLock = new Object(); /** * An interface for an adapter between a remote collection view (ListView, GridView, etc) and @@ -162,6 +160,16 @@ public abstract class RemoteViewsService extends Service { public synchronized boolean hasStableIds() { return mFactory.hasStableIds(); } + public void onDestroy(Intent intent) { + synchronized (sLock) { + Intent.FilterComparison fc = new Intent.FilterComparison(intent); + if (RemoteViewsService.sRemoteViewFactories.containsKey(fc)) { + RemoteViewsFactory factory = RemoteViewsService.sRemoteViewFactories.get(fc); + factory.onDestroy(); + RemoteViewsService.sRemoteViewFactories.remove(fc); + } + } + } private RemoteViewsFactory mFactory; private boolean mIsCreated; @@ -169,17 +177,17 @@ public abstract class RemoteViewsService extends Service { @Override public IBinder onBind(Intent intent) { - synchronized (mLock) { + synchronized (sLock) { Intent.FilterComparison fc = new Intent.FilterComparison(intent); RemoteViewsFactory factory = null; boolean isCreated = false; - if (!mRemoteViewFactories.containsKey(fc)) { + if (!sRemoteViewFactories.containsKey(fc)) { factory = onGetViewFactory(intent); - mRemoteViewFactories.put(fc, factory); + sRemoteViewFactories.put(fc, factory); factory.onCreate(); isCreated = false; } else { - factory = mRemoteViewFactories.get(fc); + factory = sRemoteViewFactories.get(fc); isCreated = true; } return new RemoteViewsFactoryAdapter(factory, isCreated); diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl index 60eca00..5857acb 100644 --- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl +++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl @@ -16,11 +16,13 @@ package com.android.internal.widget; +import android.content.Intent; import android.widget.RemoteViews; /** {@hide} */ interface IRemoteViewsFactory { void onDataSetChanged(); + void onDestroy(in Intent intent); int getCount(); RemoteViews getViewAt(int position); RemoteViews getLoadingView(); 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.FileInputStream; 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.content.res.XmlResourceParser; 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.Slog; 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 @@ class AppWidgetService extends IAppWidgetService.Stub } } - // 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 @@ class AppWidgetService extends IAppWidgetService.Stub } } + // Binds to a specific RemoteViewsService public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) { synchronized (mAppWidgetIds) { AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); @@ -452,8 +456,8 @@ class AppWidgetService extends IAppWidgetService.Stub // 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 @@ class AppWidgetService extends IAppWidgetService.Stub } 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 @@ class AppWidgetService extends IAppWidgetService.Stub } } + // 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 @@ class AppWidgetService extends IAppWidgetService.Stub 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) { |