diff options
Diffstat (limited to 'services/java/com/android/server/AppWidgetService.java')
-rw-r--r-- | services/java/com/android/server/AppWidgetService.java | 248 |
1 files changed, 227 insertions, 21 deletions
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 731fb22..59a540b 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.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; @@ -144,6 +202,7 @@ class AppWidgetService extends IAppWidgetService.Stub // update the provider list. IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); mContext.registerReceiver(mBroadcastReceiver, filter); @@ -293,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); @@ -375,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); + mBoundRemoteViewsServices.remove(key); + conn.onServiceDisconnected(null); + mContext.unbindService(conn); + } + } + } + + 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); @@ -426,6 +559,40 @@ class AppWidgetService extends IAppWidgetService.Stub } } + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + for (int i=0; i<N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + updateAppWidgetInstanceLocked(id, views, true); + } + } + } + + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + for (int i=0; i<N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); + } + } + } + public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { synchronized (mAppWidgetIds) { Provider p = lookupProviderLocked(provider); @@ -443,11 +610,17 @@ class AppWidgetService extends IAppWidgetService.Stub } void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { + updateAppWidgetInstanceLocked(id, views, false); + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { // allow for stale appWidgetIds and other badness // lookup also checks that the calling process can access the appWidgetId // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - id.views = views; + + // We do not want to save this RemoteViews + if (!isPartialUpdate) id.views = views; // is anyone listening? if (id.host.callbacks != null) { @@ -463,6 +636,25 @@ class AppWidgetService extends IAppWidgetService.Stub } } + void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { + // allow for stale appWidgetIds and other badness + // lookup also checks that the calling process can access the appWidgetId + // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) + if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { + // is anyone listening? + if (id.host.callbacks != null) { + try { + // the lock is held, but this is a oneway call + id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); + } catch (RemoteException e) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this instance. + id.host.callbacks = null; + } + } + } + } + public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, List<RemoteViews> updatedViews) { int callingUid = enforceCallingUid(packageName); @@ -584,6 +776,12 @@ class AppWidgetService extends IAppWidgetService.Stub } boolean addProviderLocked(ResolveInfo ri) { + if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + return false; + } + if (!ri.activityInfo.isEnabled()) { + return false; + } Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name), ri); if (p != null) { @@ -742,6 +940,11 @@ class AppWidgetService extends IAppWidgetService.Stub } info.label = activityInfo.loadLabel(mPackageManager).toString(); info.icon = ri.getIconResource(); + info.previewImage = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); + info.autoAdvanceViewId = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1); + sa.recycle(); } catch (Exception e) { // Ok to catch Exception here, because anything going wrong because @@ -1096,6 +1299,7 @@ class AppWidgetService extends IAppWidgetService.Stub } } else { boolean added = false; + boolean changed = false; String pkgList[] = null; if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); @@ -1114,14 +1318,16 @@ class AppWidgetService extends IAppWidgetService.Stub } pkgList = new String[] { pkgName }; added = Intent.ACTION_PACKAGE_ADDED.equals(action); + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); } if (pkgList == null || pkgList.length == 0) { return; } - if (added) { + if (added || changed) { synchronized (mAppWidgetIds) { Bundle extras = intent.getExtras(); - if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { + if (changed || (extras != null && + extras.getBoolean(Intent.EXTRA_REPLACING, false))) { for (String pkgName : pkgList) { // The package was just upgraded updateProvidersForPackageLocked(pkgName); |