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="&quot;android.permission.BIND_REMOTEVIEWS&quot;"
+ 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="&quot;android.permission.BIND_REMOTEVIEWS&quot;"
+ 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);