am aac19783: Merge "Refactoring app widgets to address security/performance issues." into honeycomb

* commit 'aac197833f3b2deddc6b3da5c144be36721d9547':
  Refactoring app widgets to address security/performance issues.
This commit is contained in:
Winson Chung
2011-01-18 23:05:38 -08:00
committed by Android Git Automerger
11 changed files with 376 additions and 37 deletions

View File

@ -167,6 +167,7 @@ LOCAL_SRC_FILES += \
core/java/com/android/internal/view/IInputMethodManager.aidl \ core/java/com/android/internal/view/IInputMethodManager.aidl \
core/java/com/android/internal/view/IInputMethodSession.aidl \ core/java/com/android/internal/view/IInputMethodSession.aidl \
core/java/com/android/internal/widget/IRemoteViewsFactory.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/ICountryDetector.aidl \
location/java/android/location/ICountryListener.aidl \ location/java/android/location/ICountryListener.aidl \
location/java/android/location/IGeocodeProvider.aidl \ location/java/android/location/IGeocodeProvider.aidl \

View File

@ -188,6 +188,17 @@
visibility="public" visibility="public"
> >
</field> </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" <field name="BIND_WALLPAPER"
type="java.lang.String" type="java.lang.String"
transient="false" transient="false"
@ -252332,6 +252343,23 @@
<parameter name="intent" type="android.content.Intent"> <parameter name="intent" type="android.content.Intent">
</parameter> </parameter>
</method> </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" <method name="setScrollPosition"
return="void" return="void"
abstract="false" abstract="false"

View File

@ -188,6 +188,17 @@
visibility="public" visibility="public"
> >
</field> </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" <field name="BIND_WALLPAPER"
type="java.lang.String" type="java.lang.String"
transient="false" transient="false"
@ -253918,6 +253929,23 @@
<parameter name="intent" type="android.content.Intent"> <parameter name="intent" type="android.content.Intent">
</parameter> </parameter>
</method> </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" <method name="setScrollPosition"
return="void" return="void"
abstract="false" abstract="false"
@ -261711,7 +261739,7 @@
deprecated="not deprecated" deprecated="not deprecated"
visibility="public" visibility="public"
> >
<parameter name="arg0" type="T"> <parameter name="t" type="T">
</parameter> </parameter>
</method> </method>
</interface> </interface>

View File

@ -18,6 +18,7 @@ package android.appwidget;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
@ -437,6 +438,47 @@ public class AppWidgetManager {
} }
} }
/**
* 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 * Get the list of appWidgetIds that have been bound to the given AppWidget
* provider. * provider.

View File

@ -58,6 +58,12 @@ public class RemoteViews implements Parcelable, Filter {
private static final String LOG_TAG = "RemoteViews"; 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 * The package name of the package containing the layout
* resource. (Added to the parcel) * resource. (Added to the parcel)
@ -1276,6 +1282,22 @@ public class RemoteViews implements Parcelable, Filter {
* providing data to the RemoteViewsAdapter * providing data to the RemoteViewsAdapter
*/ */
public void setRemoteAdapter(int viewId, Intent intent) { 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); setIntent(viewId, "setRemoteViewsAdapter", intent);
} }

View File

@ -22,20 +22,21 @@ import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import android.content.ComponentName; import android.appwidget.AppWidgetManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.View.MeasureSpec; import android.view.View.MeasureSpec;
import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory; import com.android.internal.widget.IRemoteViewsFactory;
/** /**
@ -43,11 +44,22 @@ import com.android.internal.widget.IRemoteViewsFactory;
* to be later inflated as child views. * to be later inflated as child views.
*/ */
/** @hide */ /** @hide */
public class RemoteViewsAdapter extends BaseAdapter { public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
private static final String TAG = "RemoteViewsAdapter"; private static final String TAG = "RemoteViewsAdapter";
private Context mContext; // The max number of items in the cache
private Intent mIntent; 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 LayoutInflater mLayoutInflater;
private RemoteViewsAdapterServiceConnection mServiceConnection; private RemoteViewsAdapterServiceConnection mServiceConnection;
private WeakReference<RemoteAdapterConnectionCallback> mCallback; private WeakReference<RemoteAdapterConnectionCallback> mCallback;
@ -79,7 +91,8 @@ public class RemoteViewsAdapter extends BaseAdapter {
* garbage collected, and would cause us to leak activities due to the caching mechanism for * garbage collected, and would cause us to leak activities due to the caching mechanism for
* FrameLayouts in the adapter). * FrameLayouts in the adapter).
*/ */
private static class RemoteViewsAdapterServiceConnection implements ServiceConnection { private static class RemoteViewsAdapterServiceConnection extends
IRemoteViewsAdapterConnection.Stub {
private boolean mConnected; private boolean mConnected;
private WeakReference<RemoteViewsAdapter> mAdapter; private WeakReference<RemoteViewsAdapter> mAdapter;
private IRemoteViewsFactory mRemoteViewsFactory; private IRemoteViewsFactory mRemoteViewsFactory;
@ -88,8 +101,7 @@ public class RemoteViewsAdapter extends BaseAdapter {
mAdapter = new WeakReference<RemoteViewsAdapter>(adapter); mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
} }
public void onServiceConnected(ComponentName name, public void onServiceConnected(IBinder service) {
IBinder service) {
mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service); mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
mConnected = true; mConnected = true;
@ -137,7 +149,7 @@ public class RemoteViewsAdapter extends BaseAdapter {
}); });
} }
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected() {
mConnected = false; mConnected = false;
mRemoteViewsFactory = null; mRemoteViewsFactory = null;
@ -145,8 +157,9 @@ public class RemoteViewsAdapter extends BaseAdapter {
if (adapter == null) return; if (adapter == null) return;
// Clear the main/worker queues // Clear the main/worker queues
adapter.mMainQueue.removeMessages(0); adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
adapter.mWorkerQueue.removeMessages(0); adapter.mMainQueue.removeMessages(sDefaultMessageType);
adapter.mWorkerQueue.removeMessages(sDefaultMessageType);
final RemoteAdapterConnectionCallback callback = adapter.mCallback.get(); final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
if (callback != null) { if (callback != null) {
@ -574,20 +587,26 @@ public class RemoteViewsAdapter extends BaseAdapter {
public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) { public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
mContext = context; mContext = context;
mIntent = intent; mIntent = intent;
mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
mLayoutInflater = LayoutInflater.from(context); mLayoutInflater = LayoutInflater.from(context);
if (mIntent == null) { if (mIntent == null) {
throw new IllegalArgumentException("Non-null Intent must be specified."); throw new IllegalArgumentException("Non-null Intent must be specified.");
} }
mRequestedViews = new RemoteViewsFrameLayoutRefSet(); 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 = new HandlerThread("RemoteViewsCache-loader");
mWorkerThread.start(); mWorkerThread.start();
mWorkerQueue = new Handler(mWorkerThread.getLooper()); 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 // Initialize the cache and the service connection on startup
mCache = new FixedSizeRemoteViewsCache(50); mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback); mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
mServiceConnection = new RemoteViewsAdapterServiceConnection(this); mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
requestBindService(); requestBindService();
@ -687,6 +706,7 @@ public class RemoteViewsAdapter extends BaseAdapter {
@Override @Override
public void run() { public void run() {
mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId); mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
enqueueDeferredUnbindServiceMessage();
} }
}); });
} }
@ -879,10 +899,34 @@ public class RemoteViewsAdapter extends BaseAdapter {
super.notifyDataSetChanged(); 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() { 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()) { 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(); return mServiceConnection.isConnected();

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,8 +17,10 @@
package com.android.internal.appwidget; package com.android.internal.appwidget;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent;
import android.appwidget.AppWidgetProviderInfo; import android.appwidget.AppWidgetProviderInfo;
import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetHost;
import android.os.IBinder;
import android.widget.RemoteViews; import android.widget.RemoteViews;
/** {@hide} */ /** {@hide} */
@ -46,6 +48,8 @@ interface IAppWidgetService {
List<AppWidgetProviderInfo> getInstalledProviders(); List<AppWidgetProviderInfo> getInstalledProviders();
AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId); AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId);
void bindAppWidgetId(int appWidgetId, in ComponentName provider); 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); int[] getAppWidgetIds(in ComponentName provider);
} }

View File

@ -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();
}

View File

@ -1210,6 +1210,13 @@
android:description="@string/permdesc_backup" android:description="@string/permdesc_backup"
android:protectionLevel="signatureOrSystem" /> 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 <!-- Allows an application to tell the AppWidget service which application
can access AppWidget's data. The normal user flow is that a user 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 picks an AppWidget to go into a particular host, thereby giving that

View File

@ -680,6 +680,12 @@
<string name="permdesc_bindWallpaper">Allows the holder to bind to the top-level <string name="permdesc_bindWallpaper">Allows the holder to bind to the top-level
interface of a wallpaper. Should never be needed for normal applications.</string> 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. --> <!-- 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> <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. --> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->

View File

@ -16,6 +16,23 @@
package com.android.server; 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.AlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
@ -24,46 +41,37 @@ import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.Intent.FilterComparison;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.content.res.XmlResourceParser; import android.content.res.XmlResourceParser;
import android.net.Uri; import android.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process; import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.util.Slog; import android.util.Slog;
import android.util.TypedValue; import android.util.TypedValue;
import android.util.Xml; import android.util.Xml;
import android.widget.RemoteViews; 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.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FastXmlSerializer;
import com.android.internal.widget.IRemoteViewsAdapterConnection;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
class AppWidgetService extends IAppWidgetService.Stub class AppWidgetService extends IAppWidgetService.Stub
{ {
@ -107,6 +115,56 @@ class AppWidgetService extends IAppWidgetService.Stub
Host host; 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; Context mContext;
Locale mLocale; Locale mLocale;
PackageManager mPackageManager; PackageManager mPackageManager;
@ -294,6 +352,9 @@ class AppWidgetService extends IAppWidgetService.Stub
} }
void deleteAppWidgetLocked(AppWidgetId id) { void deleteAppWidgetLocked(AppWidgetId id) {
// We first unbind all services that are bound to this id
unbindAppWidgetRemoteViewsServicesLocked(id);
Host host = id.host; Host host = id.host;
host.instances.remove(id); host.instances.remove(id);
pruneHostLocked(host); pruneHostLocked(host);
@ -376,6 +437,77 @@ class AppWidgetService extends IAppWidgetService.Stub
} }
} }
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) { public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
synchronized (mAppWidgetIds) { synchronized (mAppWidgetIds) {
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);