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/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 \

View File

@ -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"

View File

@ -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"
@ -253918,6 +253929,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"
@ -261711,7 +261739,7 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="arg0" type="T">
<parameter name="t" type="T">
</parameter>
</method>
</interface>

View File

@ -18,6 +18,7 @@ package android.appwidget;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
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
* provider.

View File

@ -58,6 +58,12 @@ public class RemoteViews implements Parcelable, Filter {
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 @@ public class RemoteViews implements Parcelable, Filter {
* 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);
}

View File

@ -22,20 +22,21 @@ import java.util.HashSet;
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 @@ import com.android.internal.widget.IRemoteViewsFactory;
* 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 @@ public class RemoteViewsAdapter extends BaseAdapter {
* 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 @@ public class RemoteViewsAdapter extends BaseAdapter {
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 class RemoteViewsAdapter extends BaseAdapter {
});
}
public void onServiceDisconnected(ComponentName name) {
public void onServiceDisconnected() {
mConnected = false;
mRemoteViewsFactory = null;
@ -145,8 +157,9 @@ public class RemoteViewsAdapter extends BaseAdapter {
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 class RemoteViewsAdapter extends BaseAdapter {
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 @@ public class RemoteViewsAdapter extends BaseAdapter {
@Override
public void run() {
mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
enqueueDeferredUnbindServiceMessage();
}
});
}
@ -879,10 +899,34 @@ public class RemoteViewsAdapter extends BaseAdapter {
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();

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");
* 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 @@ interface IAppWidgetService {
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);
}

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: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

View File

@ -680,6 +680,12 @@
<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>
<!-- 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. -->

View File

@ -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.BroadcastReceiver;
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 @@ class AppWidgetService extends IAppWidgetService.Stub
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 @@ class AppWidgetService extends IAppWidgetService.Stub
}
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 @@ 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) {
synchronized (mAppWidgetIds) {
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);