Refactoring app widgets to address security/performance issues.
- Moving the service binding to AppWidgetService to prevent arbitrary apps from binding to widget services
- Requiring RemoteViewsServices to require android.permission.BIND_REMOTEVIEWS permission
Change-Id: Id135bafba998299eb278067712b8a5d8487cfd04
diff --git a/Android.mk b/Android.mk
index f581515..5d989d1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -167,6 +167,7 @@
core/java/com/android/internal/view/IInputMethodManager.aidl \
core/java/com/android/internal/view/IInputMethodSession.aidl \
core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
+ core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl \
location/java/android/location/ICountryDetector.aidl \
location/java/android/location/ICountryListener.aidl \
location/java/android/location/IGeocodeProvider.aidl \
diff --git a/api/11.xml b/api/11.xml
index 5087eca..6c06a0a 100644
--- a/api/11.xml
+++ b/api/11.xml
@@ -188,6 +188,17 @@
visibility="public"
>
</field>
+<field name="BIND_REMOTEVIEWS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.permission.BIND_REMOTEVIEWS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="BIND_WALLPAPER"
type="java.lang.String"
transient="false"
@@ -252332,6 +252343,23 @@
<parameter name="intent" type="android.content.Intent">
</parameter>
</method>
+<method name="setRemoteAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="appWidgetId" type="int">
+</parameter>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
<method name="setScrollPosition"
return="void"
abstract="false"
diff --git a/api/current.xml b/api/current.xml
index a8ab943..9fd8e07 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -188,6 +188,17 @@
visibility="public"
>
</field>
+<field name="BIND_REMOTEVIEWS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.permission.BIND_REMOTEVIEWS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="BIND_WALLPAPER"
type="java.lang.String"
transient="false"
@@ -252455,6 +252466,23 @@
<parameter name="intent" type="android.content.Intent">
</parameter>
</method>
+<method name="setRemoteAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="appWidgetId" type="int">
+</parameter>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
<method name="setScrollPosition"
return="void"
abstract="false"
@@ -260248,7 +260276,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/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 2a583c1..019652c 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -438,6 +439,47 @@
}
/**
+ * Binds the RemoteViewsService for a given appWidgetId and intent.
+ *
+ * The appWidgetId specified must already be bound to the calling AppWidgetHost via
+ * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
+ *
+ * @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService.
+ * @param intent The intent of the service which will be providing the data to the
+ * RemoteViewsAdapter.
+ * @param connection The callback interface to be notified when a connection is made or lost.
+ * @hide
+ */
+ public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
+ try {
+ sService.bindRemoteViewsService(appWidgetId, intent, connection);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Unbinds the RemoteViewsService for a given appWidgetId and intent.
+ *
+ * The appWidgetId specified muse already be bound to the calling AppWidgetHost via
+ * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
+ *
+ * @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService.
+ * @param intent The intent of the service which will be providing the data to the
+ * RemoteViewsAdapter.
+ * @hide
+ */
+ public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
+ try {
+ sService.unbindRemoteViewsService(appWidgetId, intent);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
* Get the list of appWidgetIds that have been bound to the given AppWidget
* provider.
*
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 8f25311..c336ccb 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -59,6 +59,12 @@
private static final String LOG_TAG = "RemoteViews";
/**
+ * The intent extra that contains the appWidgetId.
+ * @hide
+ */
+ static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId";
+
+ /**
* The package name of the package containing the layout
* resource. (Added to the parcel)
*/
@@ -1276,6 +1282,22 @@
* providing data to the RemoteViewsAdapter
*/
public void setRemoteAdapter(int viewId, Intent intent) {
+ // Do nothing. This method will be removed after all widgets have been updated to the
+ // new API.
+ }
+
+ /**
+ * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
+ *
+ * @param appWidgetId The id of the app widget which contains the specified view
+ * @param viewId The id of the view whose text should change
+ * @param intent The intent of the service which will be
+ * providing data to the RemoteViewsAdapter
+ */
+ public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
+ // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
+ // RemoteViewsService
+ intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, appWidgetId);
setIntent(viewId, "setRemoteViewsAdapter", intent);
}
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index ab69725..d31c0e7 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -22,20 +22,21 @@
import java.util.LinkedList;
import java.util.Map;
-import android.content.ComponentName;
+import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
+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 com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
/**
@@ -43,11 +44,22 @@
* to be later inflated as child views.
*/
/** @hide */
-public class RemoteViewsAdapter extends BaseAdapter {
+public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
private static final String TAG = "RemoteViewsAdapter";
- private Context mContext;
- private Intent mIntent;
+ // The max number of items in the cache
+ private static final int sDefaultCacheSize = 36;
+ // 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;
+ // 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;
+
+ private final Context mContext;
+ private final Intent mIntent;
+ private final int mAppWidgetId;
private LayoutInflater mLayoutInflater;
private RemoteViewsAdapterServiceConnection mServiceConnection;
private WeakReference<RemoteAdapterConnectionCallback> mCallback;
@@ -79,7 +91,8 @@
* garbage collected, and would cause us to leak activities due to the caching mechanism for
* FrameLayouts in the adapter).
*/
- private static class RemoteViewsAdapterServiceConnection implements ServiceConnection {
+ private static class RemoteViewsAdapterServiceConnection extends
+ IRemoteViewsAdapterConnection.Stub {
private boolean mConnected;
private WeakReference<RemoteViewsAdapter> mAdapter;
private IRemoteViewsFactory mRemoteViewsFactory;
@@ -88,8 +101,7 @@
mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
}
- public void onServiceConnected(ComponentName name,
- IBinder service) {
+ public void onServiceConnected(IBinder service) {
mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
mConnected = true;
@@ -137,7 +149,7 @@
});
}
- public void onServiceDisconnected(ComponentName name) {
+ public void onServiceDisconnected() {
mConnected = false;
mRemoteViewsFactory = null;
@@ -145,8 +157,9 @@
if (adapter == null) return;
// Clear the main/worker queues
- adapter.mMainQueue.removeMessages(0);
- adapter.mWorkerQueue.removeMessages(0);
+ adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
+ adapter.mMainQueue.removeMessages(sDefaultMessageType);
+ adapter.mWorkerQueue.removeMessages(sDefaultMessageType);
final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
if (callback != null) {
@@ -574,20 +587,26 @@
public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
mContext = context;
mIntent = intent;
+ mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
mLayoutInflater = LayoutInflater.from(context);
if (mIntent == null) {
throw new IllegalArgumentException("Non-null Intent must be specified.");
}
mRequestedViews = new RemoteViewsFrameLayoutRefSet();
- // initialize the worker thread
+ // Strip the previously injected app widget id from service intent
+ if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
+ intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
+ }
+
+ // Initialize the worker thread
mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
mWorkerThread.start();
mWorkerQueue = new Handler(mWorkerThread.getLooper());
- mMainQueue = new Handler(Looper.myLooper());
+ mMainQueue = new Handler(Looper.myLooper(), this);
- // initialize the cache and the service connection on startup
- mCache = new FixedSizeRemoteViewsCache(50);
+ // Initialize the cache and the service connection on startup
+ mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
requestBindService();
@@ -687,6 +706,7 @@
@Override
public void run() {
mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
+ enqueueDeferredUnbindServiceMessage();
}
});
}
@@ -879,10 +899,34 @@
super.notifyDataSetChanged();
}
+ @Override
+ public boolean handleMessage(Message msg) {
+ boolean result = false;
+ switch (msg.what) {
+ case sUnbindServiceMessageType:
+ final AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
+ if (mServiceConnection.isConnected()) {
+ mgr.unbindRemoteViewsService(mAppWidgetId, mIntent);
+ }
+ result = true;
+ break;
+ default:
+ break;
+ }
+ return result;
+ }
+
+ private void enqueueDeferredUnbindServiceMessage() {
+ // 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)
+ // Try binding the service (which will start it if it's not already running)
if (!mServiceConnection.isConnected()) {
- mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ final AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
+ mgr.bindRemoteViewsService(mAppWidgetId, mIntent, mServiceConnection.asBinder());
}
return mServiceConnection.isConnected();
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 4d56745..fa0873d 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,10 @@
package com.android.internal.appwidget;
import android.content.ComponentName;
+import android.content.Intent;
import android.appwidget.AppWidgetProviderInfo;
import com.android.internal.appwidget.IAppWidgetHost;
+import android.os.IBinder;
import android.widget.RemoteViews;
/** {@hide} */
@@ -46,6 +48,8 @@
List<AppWidgetProviderInfo> getInstalledProviders();
AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId);
void bindAppWidgetId(int appWidgetId, in ComponentName provider);
+ void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection);
+ void unbindRemoteViewsService(int appWidgetId, in Intent intent);
int[] getAppWidgetIds(in ComponentName provider);
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
new file mode 100644
index 0000000..7eb2aef
--- /dev/null
+++ b/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.os.IBinder;
+
+/** {@hide} */
+interface IRemoteViewsAdapterConnection {
+ void onServiceConnected(IBinder service);
+ void onServiceDisconnected();
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 41c911a..9b890fa 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1210,6 +1210,13 @@
android:description="@string/permdesc_backup"
android:protectionLevel="signatureOrSystem" />
+ <!-- Must be required by a {@link android.widget.RemoteViewsService},
+ to ensure that only the system can bind to it. -->
+ <permission android:name="android.permission.BIND_REMOTEVIEWS"
+ android:label="@string/permlab_bindRemoteViews"
+ android:description="@string/permdesc_bindRemoteViews"
+ android:protectionLevel="signatureOrSystem" />
+
<!-- Allows an application to tell the AppWidget service which application
can access AppWidget's data. The normal user flow is that a user
picks an AppWidget to go into a particular host, thereby giving that
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7550905..9a1b42d 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -681,6 +681,12 @@
interface of a wallpaper. Should never be needed for normal applications.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_bindRemoteViews">bind to a widget service</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_bindRemoteViews">Allows the holder to bind to the top-level
+ interface of a widget service. Should never be needed for normal applications.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_bindDeviceAdmin">interact with a device admin</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_bindDeviceAdmin">Allows the holder to send intents to
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 22dd804..0b56f2a 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -16,6 +16,23 @@
package com.android.server;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
@@ -24,46 +41,37 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.Intent.FilterComparison;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
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;
import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
import android.util.Xml;
import android.widget.RemoteViews;
-import java.io.IOException;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.HashMap;
-import java.util.HashSet;
-
-import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.appwidget.IAppWidgetHost;
+import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.util.FastXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
+import com.android.internal.widget.IRemoteViewsAdapterConnection;
class AppWidgetService extends IAppWidgetService.Stub
{
@@ -107,6 +115,56 @@
Host host;
}
+ /**
+ * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection.
+ * This needs to be a static inner class since a reference to the ServiceConnection is held
+ * 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;
+ mKey = key;
+ mConnectionCb = connectionCb;
+ }
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ IRemoteViewsAdapterConnection cb =
+ IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
+ try {
+ cb.onServiceConnected(service);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ public void onServiceDisconnected(ComponentName name) {
+ 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();
+ }
+ }
+ }
+
+ // 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;
PackageManager mPackageManager;
@@ -294,6 +352,9 @@
}
void deleteAppWidgetLocked(AppWidgetId id) {
+ // We first unbind all services that are bound to this id
+ unbindAppWidgetRemoteViewsServicesLocked(id);
+
Host host = id.host;
host.instances.remove(id);
pruneHostLocked(host);
@@ -376,6 +437,77 @@
}
}
+ public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
+ synchronized (mAppWidgetIds) {
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
+ if (id == null) {
+ throw new IllegalArgumentException("bad appWidgetId");
+ }
+ final ComponentName componentName = intent.getComponent();
+ try {
+ final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName,
+ PackageManager.GET_PERMISSIONS);
+ if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) {
+ throw new SecurityException("Selected service does not require "
+ + android.Manifest.permission.BIND_REMOTEVIEWS
+ + ": " + componentName);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown component " + componentName);
+ }
+
+ // Bind to the RemoteViewsService (which will trigger a callback to the
+ // RemoteViewsAdapter)
+ Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
+ new FilterComparison(intent));
+ final ServiceConnection conn = new ServiceConnectionProxy(this, key, connection);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
+ mBoundRemoteViewsServices.put(key, conn);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ 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);
+ conn.onServiceDisconnected(null);
+ mContext.unbindService(conn);
+ mBoundRemoteViewsServices.remove(key);
+ }
+ }
+ }
+
+ private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
+ 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);
+ mContext.unbindService(conn);
+ }
+ }
+ }
+
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
synchronized (mAppWidgetIds) {
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);