summaryrefslogtreecommitdiffstats
path: root/services/appwidget
diff options
context:
space:
mode:
authorSvetoslav <svetoslavganov@google.com>2014-07-16 15:12:03 -0700
committerSvetoslav Ganov <svetoslavganov@google.com>2014-08-05 20:57:20 +0000
commit976e8bd2017d0263216c62111454438cc0f130e3 (patch)
tree5cf592fb85841f9e41d3bf6b43422641c3609ab2 /services/appwidget
parentc79eabcd3c6306bb2ec75f9584b79e661f265127 (diff)
downloadframeworks_base-976e8bd2017d0263216c62111454438cc0f130e3.zip
frameworks_base-976e8bd2017d0263216c62111454438cc0f130e3.tar.gz
frameworks_base-976e8bd2017d0263216c62111454438cc0f130e3.tar.bz2
Allow adding widgets from user profiles.
The goal of this change is to enable support for appwidget from user profiles to the user main profile. A user profile is a user which is associated as a child of the main user profile. For example, a user may have a personal (parent) and corporate (child) profile. The device policy should be able to control whether adding a widget from a child profile and given packages is allowed. This change assumes that all packages from managed profiles are white listed. Another change will add the device policy changes. Change-Id: I267260b55d74c48b112a29979a9f59eef7a8194e
Diffstat (limited to 'services/appwidget')
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetService.java401
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java5003
2 files changed, 3147 insertions, 2257 deletions
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
index 2fa23c9..3f95427 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
@@ -16,421 +16,32 @@
package com.android.server.appwidget;
-import android.app.ActivityManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.widget.RemoteViews;
-import com.android.internal.appwidget.IAppWidgetHost;
-import com.android.internal.appwidget.IAppWidgetService;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.server.AppWidgetBackupBridge;
-import com.android.server.WidgetBackupProvider;
import com.android.server.SystemService;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-
-
/**
* SystemService that publishes an IAppWidgetService.
*/
-public class AppWidgetService extends SystemService implements WidgetBackupProvider {
-
- static final String TAG = "AppWidgetService";
-
- final Context mContext;
- final Handler mSaveStateHandler;
-
- final SparseArray<AppWidgetServiceImpl> mAppWidgetServices;
+public class AppWidgetService extends SystemService {
+ private final AppWidgetServiceImpl mImpl;
public AppWidgetService(Context context) {
super(context);
- mContext = context;
-
- mSaveStateHandler = BackgroundThread.getHandler();
-
- mAppWidgetServices = new SparseArray<AppWidgetServiceImpl>(5);
- AppWidgetServiceImpl primary = new AppWidgetServiceImpl(context, 0, mSaveStateHandler);
- mAppWidgetServices.append(0, primary);
+ mImpl = new AppWidgetServiceImpl(context);
}
@Override
public void onStart() {
- publishBinderService(Context.APPWIDGET_SERVICE, mServiceImpl);
- AppWidgetBackupBridge.register(this);
+ publishBinderService(Context.APPWIDGET_SERVICE, mImpl);
+ AppWidgetBackupBridge.register(mImpl);
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- mServiceImpl.systemRunning(isSafeMode());
- }
- }
-
-
- // backup <-> app widget service bridge surface
- @Override
- public List<String> getWidgetParticipants(int userId) {
- return mServiceImpl.getWidgetParticipants(userId);
- }
-
- @Override
- public byte[] getWidgetState(String packageName, int userId) {
- return mServiceImpl.getWidgetState(packageName, userId);
- }
-
- @Override
- public void restoreStarting(int userId) {
- mServiceImpl.restoreStarting(userId);
- }
-
- @Override
- public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
- mServiceImpl.restoreWidgetState(packageName, restoredState, userId);
- }
-
- @Override
- public void restoreFinished(int userId) {
- mServiceImpl.restoreFinished(userId);
- }
-
-
- // implementation entry point and binder service
- private final AppWidgetServiceStub mServiceImpl = new AppWidgetServiceStub();
-
- class AppWidgetServiceStub extends IAppWidgetService.Stub {
-
- private boolean mSafeMode;
-
- public void systemRunning(boolean safeMode) {
- mSafeMode = safeMode;
-
- mAppWidgetServices.get(0).systemReady(safeMode);
-
- // Register for the boot completed broadcast, so we can send the
- // ENABLE broacasts. If we try to send them now, they time out,
- // because the system isn't ready to handle them yet.
- IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
- filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- filter, null, null);
-
- // Register for configuration changes so we can update the names
- // of the widgets when the locale changes.
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null);
-
- // Register for broadcasts about package install, etc., so we can
- // update the provider list.
- filter.addAction(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- filter, null, null);
- // Register for events related to sdcard installation.
- IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- sdFilter, null, null);
-
- IntentFilter userFilter = new IntentFilter();
- userFilter.addAction(Intent.ACTION_USER_REMOVED);
- userFilter.addAction(Intent.ACTION_USER_STOPPING);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
- onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL));
- } else if (Intent.ACTION_USER_STOPPING.equals(intent.getAction())) {
- onUserStopping(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL));
- }
- }
- }, userFilter);
- }
-
- @Override
- public int allocateAppWidgetId(String packageName, int hostId, int userId)
- throws RemoteException {
- return getImplForUser(userId).allocateAppWidgetId(packageName, hostId);
- }
-
- @Override
- public int[] getAppWidgetIdsForHost(int hostId, int userId) throws RemoteException {
- return getImplForUser(userId).getAppWidgetIdsForHost(hostId);
- }
-
- @Override
- public void deleteAppWidgetId(int appWidgetId, int userId) throws RemoteException {
- getImplForUser(userId).deleteAppWidgetId(appWidgetId);
- }
-
- @Override
- public void deleteHost(int hostId, int userId) throws RemoteException {
- getImplForUser(userId).deleteHost(hostId);
- }
-
- @Override
- public void deleteAllHosts(int userId) throws RemoteException {
- getImplForUser(userId).deleteAllHosts();
- }
-
- @Override
- public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options,
- int userId) throws RemoteException {
- getImplForUser(userId).bindAppWidgetId(appWidgetId, provider, options);
- }
-
- @Override
- public boolean bindAppWidgetIdIfAllowed(
- String packageName, int appWidgetId, ComponentName provider, Bundle options,
- int userId) throws RemoteException {
- return getImplForUser(userId).bindAppWidgetIdIfAllowed(
- packageName, appWidgetId, provider, options);
- }
-
- @Override
- public boolean hasBindAppWidgetPermission(String packageName, int userId)
- throws RemoteException {
- return getImplForUser(userId).hasBindAppWidgetPermission(packageName);
- }
-
- @Override
- public void setBindAppWidgetPermission(String packageName, boolean permission, int userId)
- throws RemoteException {
- getImplForUser(userId).setBindAppWidgetPermission(packageName, permission);
+ mImpl.setSafeMode(isSafeMode());
}
-
- @Override
- public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection,
- int userId) throws RemoteException {
- getImplForUser(userId).bindRemoteViewsService(appWidgetId, intent, connection);
- }
-
- @Override
- public int[] startListening(IAppWidgetHost host, String packageName, int hostId,
- List<RemoteViews> updatedViews, int userId) throws RemoteException {
- return getImplForUser(userId).startListening(host, packageName, hostId, updatedViews);
- }
-
- public void onUserRemoved(int userId) {
- if (userId < 1) return;
- synchronized (mAppWidgetServices) {
- AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
- mAppWidgetServices.remove(userId);
-
- if (impl == null) {
- AppWidgetServiceImpl.getSettingsFile(userId).delete();
- } else {
- impl.onUserRemoved();
- }
- }
- }
-
- public void onUserStopping(int userId) {
- if (userId < 1) return;
- synchronized (mAppWidgetServices) {
- AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
- if (impl != null) {
- mAppWidgetServices.remove(userId);
- impl.onUserStopping();
- }
- }
- }
-
-
- // support of the widget/backup bridge
- public List<String> getWidgetParticipants(int userId) {
- return getImplForUser(userId).getWidgetParticipants();
- }
-
- public byte[] getWidgetState(String packageName, int userId) {
- return getImplForUser(userId).getWidgetState(packageName);
- }
-
- public void restoreStarting(int userId) {
- getImplForUser(userId).restoreStarting();
- }
-
- public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
- getImplForUser(userId).restoreWidgetState(packageName, restoredState);
- }
-
- public void restoreFinished(int userId) {
- getImplForUser(userId).restoreFinished();
- }
-
-
- private void checkPermission(int userId) {
- int realUserId = ActivityManager.handleIncomingUser(
- Binder.getCallingPid(),
- Binder.getCallingUid(),
- userId,
- false, /* allowAll */
- true, /* requireFull */
- this.getClass().getSimpleName(),
- this.getClass().getPackage().getName());
- }
-
- private AppWidgetServiceImpl getImplForUser(int userId) {
- checkPermission(userId);
- boolean sendInitial = false;
- AppWidgetServiceImpl service;
- synchronized (mAppWidgetServices) {
- service = mAppWidgetServices.get(userId);
- if (service == null) {
- Slog.i(TAG, "Unable to find AppWidgetServiceImpl for user " + userId
- + ", adding");
- // TODO: Verify that it's a valid user
- service = new AppWidgetServiceImpl(mContext, userId, mSaveStateHandler);
- service.systemReady(mSafeMode);
- // Assume that BOOT_COMPLETED was received, as this is a non-primary user.
- mAppWidgetServices.append(userId, service);
- sendInitial = true;
- }
- }
- if (sendInitial) {
- service.sendInitialBroadcasts();
- }
- return service;
- }
-
- @Override
- public int[] getAppWidgetIds(ComponentName provider, int userId) throws RemoteException {
- return getImplForUser(userId).getAppWidgetIds(provider);
- }
-
- @Override
- public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId, int userId)
- throws RemoteException {
- return getImplForUser(userId).getAppWidgetInfo(appWidgetId);
- }
-
- @Override
- public RemoteViews getAppWidgetViews(int appWidgetId, int userId) throws RemoteException {
- return getImplForUser(userId).getAppWidgetViews(appWidgetId);
- }
-
- @Override
- public void updateAppWidgetOptions(int appWidgetId, Bundle options, int userId) {
- getImplForUser(userId).updateAppWidgetOptions(appWidgetId, options);
- }
-
- @Override
- public Bundle getAppWidgetOptions(int appWidgetId, int userId) {
- return getImplForUser(userId).getAppWidgetOptions(appWidgetId);
- }
-
- @Override
- public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int userId)
- throws RemoteException {
- return getImplForUser(userId).getInstalledProviders(categoryFilter);
- }
-
- @Override
- public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId, int userId)
- throws RemoteException {
- getImplForUser(userId).notifyAppWidgetViewDataChanged(
- appWidgetIds, viewId);
- }
-
- @Override
- public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views, int userId)
- throws RemoteException {
- getImplForUser(userId).partiallyUpdateAppWidgetIds(
- appWidgetIds, views);
- }
-
- @Override
- public void stopListening(int hostId, int userId) throws RemoteException {
- getImplForUser(userId).stopListening(hostId);
- }
-
- @Override
- public void unbindRemoteViewsService(int appWidgetId, Intent intent, int userId)
- throws RemoteException {
- getImplForUser(userId).unbindRemoteViewsService(
- appWidgetId, intent);
- }
-
- @Override
- public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views, int userId)
- throws RemoteException {
- getImplForUser(userId).updateAppWidgetIds(appWidgetIds, views);
- }
-
- @Override
- public void updateAppWidgetProvider(ComponentName provider, RemoteViews views, int userId)
- throws RemoteException {
- getImplForUser(userId).updateAppWidgetProvider(provider, views);
- }
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
- // Dump the state of all the app widget providers
- synchronized (mAppWidgetServices) {
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- for (int i = 0; i < mAppWidgetServices.size(); i++) {
- pw.println("User: " + mAppWidgetServices.keyAt(i));
- ipw.increaseIndent();
- AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
- service.dump(fd, ipw, args);
- ipw.decreaseIndent();
- }
- }
- }
-
- BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- // Slog.d(TAG, "received " + action);
- if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId >= 0) {
- getImplForUser(userId).sendInitialBroadcasts();
- } else {
- Slog.w(TAG, "Incorrect user handle supplied in " + intent);
- }
- } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
- for (int i = 0; i < mAppWidgetServices.size(); i++) {
- AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
- service.onConfigurationChanged();
- }
- } else {
- int sendingUser = getSendingUserId();
- if (sendingUser == UserHandle.USER_ALL) {
- for (int i = 0; i < mAppWidgetServices.size(); i++) {
- AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
- service.onBroadcastReceived(intent);
- }
- } else {
- AppWidgetServiceImpl service = mAppWidgetServices.get(sendingUser);
- if (service != null) {
- service.onBroadcastReceived(intent);
- }
- }
- }
- }
- };
}
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 7a67d63..bdaf9ec 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -19,21 +19,25 @@ package com.android.server.appwidget;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManagerInternal;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
+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.IntentSender;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -44,16 +48,20 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.AttributeSet;
-import android.util.MutableInt;
import android.util.Pair;
import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.TypedValue;
import android.util.Xml;
import android.view.Display;
@@ -61,10 +69,16 @@ import android.view.WindowManager;
import android.widget.RemoteViews;
import com.android.internal.appwidget.IAppWidgetHost;
+import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.SomeArgs;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
+import com.android.server.LocalServices;
+import com.android.server.WidgetBackupProvider;
+import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -79,230 +93,117 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import java.util.Map.Entry;
+import java.util.Map;
import java.util.Set;
-class AppWidgetServiceImpl {
+class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider {
+ private static final String TAG = "AppWidgetServiceImpl";
+
+ private static boolean DEBUG = false;
- private static final String KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
+ private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
+ private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
private static final int KEYGUARD_HOST_ID = 0x4b455947;
- private static final String TAG = "AppWidgetServiceImpl";
- private static final String SETTINGS_FILENAME = "appwidgets.xml";
- private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes
- private static final int CURRENT_VERSION = 1; // Bump if the stored widgets need to be upgraded.
- private static final int WIDGET_STATE_VERSION = 1; // version of backed-up widget state
- private static boolean DBG = true;
- private static boolean DEBUG_BACKUP = DBG || true;
+ private static final String STATE_FILENAME = "appwidgets.xml";
- /*
- * When identifying a Host or Provider based on the calling process, use the uid field. When
- * identifying a Host or Provider based on a package manager broadcast, use the package given.
- */
+ private static final int MIN_UPDATE_PERIOD = DEBUG ? 0 : 30 * 60 * 1000; // 30 minutes
- static class Provider {
- int uid;
- AppWidgetProviderInfo info;
- ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>();
- PendingIntent broadcast;
- boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
+ private static final int TAG_UNDEFINED = -1;
- int tag; // for use while saving state (the index)
+ private static final int UNKNOWN_UID = -1;
- // is there an instance of this provider hosted by the given app?
- public boolean isHostedBy(String packageName) {
- final int N = instances.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = instances.get(i);
- if (packageName.equals(id.host.packageName)) {
- return true;
- }
- }
- return false;
- }
+ private static final int LOADED_PROFILE_ID = -1;
- @Override
- public String toString() {
- return "Provider{" + ((info == null) ? "null" : info.provider)
- + (zombie ? " Z" : "")
- + '}';
- }
- }
+ private static final int DISABLED_PROFILE = -1;
- static class Host {
- int uid;
- int hostId;
- String packageName;
- ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>();
- IAppWidgetHost callbacks;
- boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
+ private static final int UNKNOWN_USER_ID = -10;
- int tag; // for use while saving state (the index)
+ // Bump if the stored widgets need to be upgraded.
+ private static final int CURRENT_VERSION = 1;
- boolean uidMatches(int callingUid) {
- if (UserHandle.getAppId(callingUid) == Process.myUid()) {
- // For a host that's in the system process, ignore the user id
- return UserHandle.isSameApp(this.uid, callingUid);
- } else {
- return this.uid == callingUid;
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (DEBUG) {
+ Slog.i(TAG, "Received broadcast: " + action);
}
- }
- boolean hostsPackage(String pkg) {
- final int N = instances.size();
- for (int i = 0; i < N; i++) {
- Provider p = instances.get(i).provider;
- if (p != null && p.info != null && pkg.equals(p.info.provider.getPackageName())) {
- return true;
- }
+ if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
+ onConfigurationChanged();
+ } else if (Intent.ACTION_USER_STARTED.equals(action)) {
+ onUserStarted(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL));
+ } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+ onUserStopped(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL));
+ } else {
+ onPackageBroadcastReceived(intent, intent.getIntExtra(
+ Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
}
- return false;
}
+ };
- @Override
- public String toString() {
- return "Host{" + packageName + ":" + hostId + '}';
- }
- }
+ // Manages active connections to RemoteViewsServices.
+ private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
+ mBoundRemoteViewsServices = new HashMap<>();
- static class AppWidgetId {
- int appWidgetId;
- int restoredId; // tracking & remapping any restored state
- Provider provider;
- RemoteViews views;
- Bundle options;
- Host host;
+ // Manages persistent references to RemoteViewsServices from different App Widgets.
+ private final HashMap<Pair<Integer, FilterComparison>, HashSet<Integer>>
+ mRemoteViewsServicesAppWidgets = new HashMap<>();
- @Override
- public String toString() {
- return "AppWidgetId{" + appWidgetId + ':' + host + ':' + provider + '}';
- }
- }
+ private final Object mLock = new Object();
- AppWidgetId findRestoredWidgetLocked(int restoredId, Host host, Provider p) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "Find restored widget: id=" + restoredId
- + " host=" + host + " provider=" + p);
- }
+ private final ArrayList<Widget> mWidgets = new ArrayList<>();
+ private final ArrayList<Host> mHosts = new ArrayList<>();
+ private final ArrayList<Provider> mProviders = new ArrayList<>();
- if (p == null || host == null) {
- return null;
- }
+ private final ArraySet<Pair<Integer, String>> mPackagesWithBindWidgetPermission =
+ new ArraySet<>();
- final int N = mAppWidgetIds.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId widget = mAppWidgetIds.get(i);
- if (widget.restoredId == restoredId
- && widget.host.hostId == host.hostId
- && widget.host.packageName.equals(host.packageName)
- && widget.provider.info.provider.equals(p.info.provider)) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " Found at " + i + " : " + widget);
- }
- return widget;
- }
- }
- return null;
- }
+ private final SparseIntArray mLoadedUserIds = new SparseIntArray();
- /**
- * 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 IBinder mConnectionCb;
+ private final BackupRestoreController mBackupRestoreController;
- ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
- mConnectionCb = connectionCb;
- }
+ private final Context mContext;
- public void onServiceConnected(ComponentName name, IBinder service) {
- final IRemoteViewsAdapterConnection cb = IRemoteViewsAdapterConnection.Stub
- .asInterface(mConnectionCb);
- try {
- cb.onServiceConnected(service);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
+ private final IPackageManager mPackageManager;
+ private final AlarmManager mAlarmManager;
+ private final UserManager mUserManager;
- public void onServiceDisconnected(ComponentName name) {
- disconnect();
- }
-
- public void disconnect() {
- final IRemoteViewsAdapterConnection cb = IRemoteViewsAdapterConnection.Stub
- .asInterface(mConnectionCb);
- try {
- cb.onServiceDisconnected();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- // Manages active connections to RemoteViewsServices
- private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> mBoundRemoteViewsServices = new HashMap<Pair<Integer, FilterComparison>, ServiceConnection>();
- // Manages persistent references to RemoteViewsServices from different App Widgets
- private final HashMap<FilterComparison, HashSet<Integer>> mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>();
-
- final Context mContext;
- final IPackageManager mPm;
- final AlarmManager mAlarmManager;
- final ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>();
- final int mUserId;
- final boolean mHasFeature;
-
- Locale mLocale;
- int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1;
- final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>();
- final ArrayList<Host> mHosts = new ArrayList<Host>();
- // set of package names
- final HashSet<String> mPackagesWithBindWidgetPermission = new HashSet<String>();
- boolean mSafeMode;
- boolean mStateLoaded;
- int mMaxWidgetBitmapMemory;
-
- // Map old (restored) widget IDs to new AppWidgetId instances. This object is used
- // as the lock around manipulation of the overall restored-widget state, just as
- // mAppWidgetIds is used as the lock object around all "live" widget state
- // manipulations. Methods that must be called with this lock held are decorated
- // with the suffix "Lr".
- //
- // In cases when both of those locks must be held concurrently, mRestoredWidgetIds
- // must be acquired *first.*
- private final SparseArray<AppWidgetId> mRestoredWidgetIds = new SparseArray<AppWidgetId>();
-
- // We need to make sure to wipe the pre-restore widget state only once for
- // a given package. Keep track of what we've done so far here; the list is
- // cleared at the start of every system restore pass, but preserved through
- // any install-time restore operations.
- HashSet<String> mPrunedApps = new HashSet<String>();
+ private final SecurityPolicy mSecurityPolicy;
private final Handler mSaveStateHandler;
+ private final Handler mCallbackHandler;
+
+ private Locale mLocale;
- // These are for debugging only -- widgets are going missing in some rare instances
- ArrayList<Provider> mDeletedProviders = new ArrayList<Provider>();
- ArrayList<Host> mDeletedHosts = new ArrayList<Host>();
+ private final SparseIntArray mNextAppWidgetIds = new SparseIntArray();
- AppWidgetServiceImpl(Context context, int userId, Handler saveStateHandler) {
+ private boolean mSafeMode;
+ private int mMaxWidgetBitmapMemory;
+
+ AppWidgetServiceImpl(Context context) {
mContext = context;
- mPm = AppGlobals.getPackageManager();
+ mPackageManager = AppGlobals.getPackageManager();
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- mUserId = userId;
- mSaveStateHandler = saveStateHandler;
- mHasFeature = context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_APP_WIDGETS);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mSaveStateHandler = BackgroundThread.getHandler();
+ mCallbackHandler = new CallbackHandler(mContext.getMainLooper());
+ mBackupRestoreController = new BackupRestoreController();
+ mSecurityPolicy = new SecurityPolicy();
computeMaximumWidgetBitmapMemory();
+ registerBroadcastReceiver();
}
- void computeMaximumWidgetBitmapMemory() {
+ private void computeMaximumWidgetBitmapMemory() {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
@@ -312,51 +213,99 @@ class AppWidgetServiceImpl {
mMaxWidgetBitmapMemory = 6 * size.x * size.y;
}
- public void systemReady(boolean safeMode) {
+ private void registerBroadcastReceiver() {
+ // Register for configuration changes so we can update the names
+ // of the widgets when the locale changes.
+ IntentFilter configFilter = new IntentFilter();
+ configFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ configFilter, null, null);
+
+ // Register for broadcasts about package install, etc., so we can
+ // update the provider list.
+ IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addDataScheme("package");
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ packageFilter, null, null);
+
+ // Register for events related to sdcard installation.
+ IntentFilter sdFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ sdFilter, null, null);
+
+ IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_STARTED);
+ userFilter.addAction(Intent.ACTION_USER_STOPPED);
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ userFilter, null, null);
+ }
+
+ public void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
-
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- }
}
- private void log(String msg) {
- Slog.i(TAG, "u=" + mUserId + ": " + msg);
- }
+ private void onConfigurationChanged() {
+ if (DEBUG) {
+ Slog.i(TAG, "onConfigurationChanged()");
+ }
- void onConfigurationChanged() {
- if (DBG) log("Got onConfigurationChanged()");
Locale revised = Locale.getDefault();
- if (revised == null || mLocale == null || !(revised.equals(mLocale))) {
+ if (revised == null || mLocale == null || !revised.equals(mLocale)) {
mLocale = revised;
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
+ synchronized (mLock) {
+ SparseIntArray changedGroups = null;
+
// Note: updateProvidersForPackageLocked() may remove providers, so we must copy the
// list of installed providers and skip providers that we don't need to update.
// Also note that remove the provider does not clear the Provider component data.
- ArrayList<Provider> installedProviders =
- new ArrayList<Provider>(mInstalledProviders);
- HashSet<ComponentName> removedProviders = new HashSet<ComponentName>();
+ ArrayList<Provider> installedProviders = new ArrayList<>(mProviders);
+ HashSet<ProviderId> removedProviders = new HashSet<>();
+
int N = installedProviders.size();
for (int i = N - 1; i >= 0; i--) {
- Provider p = installedProviders.get(i);
- ComponentName cn = p.info.provider;
- if (!removedProviders.contains(cn)) {
- updateProvidersForPackageLocked(cn.getPackageName(), removedProviders);
+ Provider provider = installedProviders.get(i);
+
+ ensureGroupStateLoadedLocked(provider.getUserId());
+
+ if (!removedProviders.contains(provider.id)) {
+ final boolean changed = updateProvidersForPackageLocked(
+ provider.id.componentName.getPackageName(),
+ provider.getUserId(), removedProviders);
+
+ if (changed) {
+ if (changedGroups == null) {
+ changedGroups = new SparseIntArray();
+ }
+ final int groupId = mSecurityPolicy.getGroupParent(
+ provider.getUserId());
+ changedGroups.put(groupId, groupId);
+ }
+ }
+ }
+
+ if (changedGroups != null) {
+ final int groupCount = changedGroups.size();
+ for (int i = 0; i < groupCount; i++) {
+ final int groupId = changedGroups.get(i);
+ saveGroupStateAsync(groupId);
}
}
- saveStateAsync();
}
}
}
- void onBroadcastReceived(Intent intent) {
- if (DBG) log("onBroadcast " + intent);
+ private void onPackageBroadcastReceived(Intent intent, int userId) {
final String action = intent.getAction();
boolean added = false;
boolean changed = false;
- boolean providersModified = false;
+ boolean componentsModified = false;
+
String pkgList[] = null;
if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
@@ -380,894 +329,1311 @@ class AppWidgetServiceImpl {
if (pkgList == null || pkgList.length == 0) {
return;
}
- if (added || changed) {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- Bundle extras = intent.getExtras();
- if (changed
- || (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false))) {
- for (String pkgName : pkgList) {
- // The package was just upgraded
- providersModified |= updateProvidersForPackageLocked(pkgName, null);
- }
- } else {
- // The package was just added. Fix up the providers...
- for (String pkgName : pkgList) {
- providersModified |= addProvidersForPackageLocked(pkgName);
- }
- // ...and see if these are hosts we've been awaiting
- for (String pkg : pkgList) {
- try {
- int uid = getUidForPackage(pkg);
- resolveHostUidLocked(pkg, uid);
- } catch (NameNotFoundException e) {
- // shouldn't happen; we just installed it!
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ Bundle extras = intent.getExtras();
+
+ if (added || changed) {
+ final boolean newPackageAdded = added && (extras == null
+ || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
+
+ for (String pkgName : pkgList) {
+ // Fix up the providers - add/remove/update.
+ componentsModified |= updateProvidersForPackageLocked(pkgName, userId, null);
+
+ // ... and see if these are hosts we've been awaiting.
+ // NOTE: We are backing up and restoring only the owner.
+ if (newPackageAdded && userId == UserHandle.USER_OWNER) {
+ final int uid = getUidForPackage(pkgName, userId);
+ if (uid >= 0 ) {
+ resolveHostUidLocked(pkgName, uid);
}
}
}
- saveStateAsync();
- }
- } else {
- Bundle extras = intent.getExtras();
- if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
- // The package is being updated. We'll receive a PACKAGE_ADDED shortly.
} else {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
+ // If the package is being updated, we'll receive a PACKAGE_ADDED
+ // shortly, otherwise it is removed permanently.
+ final boolean packageRemovedPermanently = (extras == null
+ || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
+
+ if (packageRemovedPermanently) {
for (String pkgName : pkgList) {
- providersModified |= removeProvidersForPackageLocked(pkgName);
- saveStateAsync();
+ componentsModified |= removeHostsAndProvidersForPackageLocked(
+ pkgName, userId);
}
}
}
- }
- if (providersModified) {
- // If the set of providers has been modified, notify each active AppWidgetHost
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- notifyHostsForProvidersChangedLocked();
+ if (componentsModified) {
+ saveGroupStateAsync(userId);
+
+ // If the set of providers has been modified, notify each active AppWidgetHost
+ scheduleNotifyHostsForProvidersChangedLocked();
}
}
}
- void resolveHostUidLocked(String pkg, int uid) {
+ private void resolveHostUidLocked(String pkg, int uid) {
final int N = mHosts.size();
for (int i = 0; i < N; i++) {
- Host h = mHosts.get(i);
- if (h.uid == -1 && pkg.equals(h.packageName)) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "host " + pkg + ":" + h.hostId + " resolved to uid " + uid);
+ Host host = mHosts.get(i);
+ if (host.id.uid == UNKNOWN_UID && pkg.equals(host.id.packageName)) {
+ if (DEBUG) {
+ Slog.i(TAG, "host " + host.id + " resolved to uid " + uid);
}
- h.uid = uid;
+ host.id = new HostId(uid, host.id.hostId, host.id.packageName);
+ return;
}
}
}
- private void dumpProvider(Provider p, int index, PrintWriter pw) {
- AppWidgetProviderInfo info = p.info;
- pw.print(" ["); pw.print(index); pw.print("] provider ");
- pw.print(info.provider.flattenToShortString());
- pw.println(':');
- pw.print(" min=("); pw.print(info.minWidth);
- pw.print("x"); pw.print(info.minHeight);
- pw.print(") minResize=("); pw.print(info.minResizeWidth);
- pw.print("x"); pw.print(info.minResizeHeight);
- pw.print(") updatePeriodMillis=");
- pw.print(info.updatePeriodMillis);
- pw.print(" resizeMode=");
- pw.print(info.resizeMode);
- pw.print(info.widgetCategory);
- pw.print(" autoAdvanceViewId=");
- pw.print(info.autoAdvanceViewId);
- pw.print(" initialLayout=#");
- pw.print(Integer.toHexString(info.initialLayout));
- pw.print(" uid="); pw.print(p.uid);
- pw.print(" zombie="); pw.println(p.zombie);
- }
-
- private void dumpHost(Host host, int index, PrintWriter pw) {
- pw.print(" ["); pw.print(index); pw.print("] hostId=");
- pw.print(host.hostId); pw.print(' ');
- pw.print(host.packageName); pw.print('/');
- pw.print(host.uid); pw.println(':');
- pw.print(" callbacks="); pw.println(host.callbacks);
- pw.print(" instances.size="); pw.print(host.instances.size());
- pw.print(" zombie="); pw.println(host.zombie);
- }
+ private void ensureGroupStateLoadedLocked(int userId) {
+ final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
- private void dumpAppWidgetId(AppWidgetId id, int index, PrintWriter pw) {
- pw.print(" ["); pw.print(index); pw.print("] id=");
- pw.println(id.appWidgetId);
- pw.print(" hostId=");
- pw.print(id.host.hostId); pw.print(' ');
- pw.print(id.host.packageName); pw.print('/');
- pw.println(id.host.uid);
- if (id.provider != null) {
- pw.print(" provider=");
- pw.println(id.provider.info.provider.flattenToShortString());
+ // Careful lad, we may have already loaded the state for some
+ // group members, so check before loading and read only the
+ // state for the new member(s).
+ int newMemberCount = 0;
+ final int profileIdCount = profileIds.length;
+ for (int i = 0; i < profileIdCount; i++) {
+ final int profileId = profileIds[i];
+ if (mLoadedUserIds.indexOfKey(profileId) >= 0) {
+ profileIds[i] = LOADED_PROFILE_ID;
+ } else {
+ newMemberCount++;
+ }
}
- if (id.host != null) {
- pw.print(" host.callbacks="); pw.println(id.host.callbacks);
+
+ if (newMemberCount <= 0) {
+ return;
}
- if (id.views != null) {
- pw.print(" views="); pw.println(id.views);
+
+ int newMemberIndex = 0;
+ final int[] newProfileIds = new int[newMemberCount];
+ for (int i = 0; i < profileIdCount; i++) {
+ final int profileId = profileIds[i];
+ if (profileId != LOADED_PROFILE_ID) {
+ mLoadedUserIds.put(profileId, profileId);
+ newProfileIds[newMemberIndex] = profileId;
+ newMemberIndex++;
+ }
}
+
+ loadGroupWidgetProvidersLocked(newProfileIds);
+ loadGroupStateLocked(newProfileIds);
}
- void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
- pw.println("Permission Denial: can't dump from from pid="
+ throw new SecurityException("Permission Denial: can't dump from from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
- return;
}
- synchronized (mAppWidgetIds) {
- int N = mInstalledProviders.size();
+ synchronized (mLock) {
+ int N = mProviders.size();
pw.println("Providers:");
- for (int i=0; i<N; i++) {
- dumpProvider(mInstalledProviders.get(i), i, pw);
+ for (int i = 0; i < N; i++) {
+ dumpProvider(mProviders.get(i), i, pw);
}
- N = mAppWidgetIds.size();
+ N = mWidgets.size();
pw.println(" ");
- pw.println("AppWidgetIds:");
- for (int i=0; i<N; i++) {
- dumpAppWidgetId(mAppWidgetIds.get(i), i, pw);
+ pw.println("Widgets:");
+ for (int i = 0; i < N; i++) {
+ dumpWidget(mWidgets.get(i), i, pw);
}
N = mHosts.size();
pw.println(" ");
pw.println("Hosts:");
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
dumpHost(mHosts.get(i), i, pw);
}
- N = mDeletedProviders.size();
+
+ N = mPackagesWithBindWidgetPermission.size();
pw.println(" ");
- pw.println("Deleted Providers:");
- for (int i=0; i<N; i++) {
- dumpProvider(mDeletedProviders.get(i), i, pw);
+ pw.println("Grants:");
+ for (int i = 0; i < N; i++) {
+ Pair<Integer, String> grant = mPackagesWithBindWidgetPermission.valueAt(i);
+ dumpGrant(grant, i, pw);
}
+ }
+ }
- N = mDeletedHosts.size();
- pw.println(" ");
- pw.println("Deleted Hosts:");
- for (int i=0; i<N; i++) {
- dumpHost(mDeletedHosts.get(i), i, pw);
+ @Override
+ public int[] startListening(IAppWidgetHost callbacks, String callingPackage,
+ int hostId, List<RemoteViews> updatedViews) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "startListening() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access hosts it owns.
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupOrAddHostLocked(id);
+
+ host.callbacks = callbacks;
+
+ updatedViews.clear();
+
+ ArrayList<Widget> instances = host.widgets;
+ int N = instances.size();
+ int[] updatedIds = new int[N];
+ for (int i = 0; i < N; i++) {
+ Widget widget = instances.get(i);
+ updatedIds[i] = widget.appWidgetId;
+ updatedViews.add(cloneIfLocalBinder(widget.views));
}
+
+ return updatedIds;
}
}
- private void ensureStateLoadedLocked() {
- if (!mStateLoaded) {
- if (!mHasFeature) {
- return;
+ @Override
+ public void stopListening(String callingPackage, int hostId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "stopListening() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access hosts it owns.
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupHostLocked(id);
+
+ if (host != null) {
+ host.callbacks = null;
+ pruneHostLocked(host);
}
- loadWidgetProviderListLocked();
- loadStateLocked();
- mStateLoaded = true;
}
}
- public int allocateAppWidgetId(String packageName, int hostId) {
- int callingUid = enforceSystemOrCallingUid(packageName);
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return -1;
+ @Override
+ public int allocateAppWidgetId(String callingPackage, int hostId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "allocateAppWidgetId() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ if (mNextAppWidgetIds.indexOfKey(userId) < 0) {
+ mNextAppWidgetIds.put(userId, AppWidgetManager.INVALID_APPWIDGET_ID + 1);
}
- ensureStateLoadedLocked();
- int appWidgetId = mNextAppWidgetId++;
- Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
+ final int appWidgetId = incrementAndGetAppWidgetIdLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access hosts it owns.
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupOrAddHostLocked(id);
- AppWidgetId id = new AppWidgetId();
- id.appWidgetId = appWidgetId;
- id.host = host;
+ Widget widget = new Widget();
+ widget.appWidgetId = appWidgetId;
+ widget.host = host;
- host.instances.add(id);
- mAppWidgetIds.add(id);
+ host.widgets.add(widget);
+ mWidgets.add(widget);
+
+ saveGroupStateAsync(userId);
+
+ if (DEBUG) {
+ Slog.i(TAG, "Allocated widget id " + appWidgetId
+ + " for host " + host.id);
+ }
- saveStateAsync();
- if (DBG) log("Allocating AppWidgetId for " + packageName + " host=" + hostId
- + " id=" + appWidgetId);
return appWidgetId;
}
}
- public void deleteAppWidgetId(int appWidgetId) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
+ @Override
+ public void deleteAppWidgetId(String callingPackage, int appWidgetId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "deleteAppWidgetId() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
return;
}
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id != null) {
- deleteAppWidgetLocked(id);
- saveStateAsync();
+
+ deleteAppWidgetLocked(widget);
+
+ saveGroupStateAsync(userId);
+
+ if (DEBUG) {
+ Slog.i(TAG, "Deleted widget id " + appWidgetId
+ + " for host " + widget.host.id);
}
}
}
- public void deleteHost(int hostId) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- ensureStateLoadedLocked();
- int callingUid = Binder.getCallingUid();
- Host host = lookupHostLocked(callingUid, hostId);
- if (host != null) {
- deleteHostLocked(host);
- saveStateAsync();
+ @Override
+ public boolean hasBindAppWidgetPermission(String packageName, int grantId) {
+ if (DEBUG) {
+ Slog.i(TAG, "hasBindAppWidgetPermission() " + UserHandle.getCallingUserId());
+ }
+
+ // A special permission is required for managing white listing.
+ mSecurityPolicy.enforceModifyAppWidgetBindPermissions(packageName);
+
+ synchronized (mLock) {
+ // The grants are stored in user state wich gets the grant.
+ ensureGroupStateLoadedLocked(grantId);
+
+ final int packageUid = getUidForPackage(packageName, grantId);
+ if (packageUid < 0) {
+ return false;
}
+
+ Pair<Integer, String> packageId = Pair.create(grantId, packageName);
+ return mPackagesWithBindWidgetPermission.contains(packageId);
}
}
- public void deleteAllHosts() {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
+ @Override
+ public void setBindAppWidgetPermission(String packageName, int grantId,
+ boolean grantPermission) {
+ if (DEBUG) {
+ Slog.i(TAG, "setBindAppWidgetPermission() " + UserHandle.getCallingUserId());
+ }
+
+ // A special permission is required for managing white listing.
+ mSecurityPolicy.enforceModifyAppWidgetBindPermissions(packageName);
+
+ synchronized (mLock) {
+ // The grants are stored in user state wich gets the grant.
+ ensureGroupStateLoadedLocked(grantId);
+
+ final int packageUid = getUidForPackage(packageName, grantId);
+ if (packageUid < 0) {
return;
}
- ensureStateLoadedLocked();
- int callingUid = Binder.getCallingUid();
- final int N = mHosts.size();
- boolean changed = false;
- for (int i = N - 1; i >= 0; i--) {
- Host host = mHosts.get(i);
- if (host.uidMatches(callingUid)) {
- deleteHostLocked(host);
- changed = true;
- }
- }
- if (changed) {
- saveStateAsync();
+
+ Pair<Integer, String> packageId = Pair.create(grantId, packageName);
+ if (grantPermission) {
+ mPackagesWithBindWidgetPermission.add(packageId);
+ } else {
+ mPackagesWithBindWidgetPermission.remove(packageId);
}
+
+ saveGroupStateAsync(grantId);
}
}
- void deleteHostLocked(Host host) {
- if (DBG) log("Deleting host " + host);
- final int N = host.instances.size();
- for (int i = N - 1; i >= 0; i--) {
- AppWidgetId id = host.instances.get(i);
- deleteAppWidgetLocked(id);
+ @Override
+ public IntentSender createAppWidgetConfigIntentSender(String callingPackage, Intent intent) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "createAppWidgetConfigIntentSender() " + userId);
}
- host.instances.clear();
- mHosts.remove(host);
- mDeletedHosts.add(host);
- // it's gone or going away, abruptly drop the callback connection
- host.callbacks = null;
- }
- void deleteAppWidgetLocked(AppWidgetId id) {
- // We first unbind all services that are bound to this id
- unbindAppWidgetRemoteViewsServicesLocked(id);
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
- Host host = id.host;
- host.instances.remove(id);
- pruneHostLocked(host);
+ // The only allowed action is the one to start the configure activity.
+ if (!AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(intent.getAction())) {
+ throw new IllegalArgumentException("Only allowed action is "
+ + AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
+ }
- mAppWidgetIds.remove(id);
+ // Verify that widget id is provided.
+ final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+ if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ throw new IllegalArgumentException("Widget id required");
+ }
- Provider p = id.provider;
- if (p != null) {
- p.instances.remove(id);
- if (!p.zombie) {
- // send the broacast saying that this appWidgetId has been deleted
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
- intent.setComponent(p.info.provider);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
- mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
- if (p.instances.size() == 0) {
- // cancel the future updates
- cancelBroadcasts(p);
+ // Make sure a component name is provided.
+ ComponentName component = intent.getComponent();
+ if (component == null) {
+ throw new IllegalArgumentException("Component name required");
+ }
- // send the broacast saying that the provider is not in use any more
- intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
- intent.setComponent(p.info.provider);
- mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
+ // Verify the user handle.
+ UserHandle userHandle = intent.getParcelableExtra(
+ AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
+ if (userHandle != null) {
+ // Remove the profile extra as the receiver already runs under this
+ // user and this information is of no use to this receiver.
+ intent.removeExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
+
+ // If the user handle is not the caller, check if it is an enabled
+ // profile for which the package is white-listed.
+ final int profileId = userHandle.getIdentifier();
+ if (profileId != userId) {
+ // Make sure the passed user handle is a profile in the group.
+ final int[] profileIds = mSecurityPolicy.resolveCallerEnabledGroupProfiles(
+ new int[]{profileId});
+ if (profileIds.length <= 0) {
+ // The profile is not in the group or not enabled, done.
+ return null;
+ }
+
+ // Make sure the provider is white-listed.
+ if (!mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ component.getPackageName(), profileId)) {
+ throw new IllegalArgumentException("Cannot access provider "
+ + component + " in user " + profileIds);
}
}
+ } else {
+ // If a profile is not specified use the caller user id.
+ userHandle = new UserHandle(userId);
}
- }
- void cancelBroadcasts(Provider p) {
- if (DBG) log("cancelBroadcasts for " + p);
- if (p.broadcast != null) {
- mAlarmManager.cancel(p.broadcast);
- long token = Binder.clearCallingIdentity();
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
+ throw new IllegalArgumentException("Bad widget id " + appWidgetId);
+ }
+
+ Provider provider = widget.provider;
+ if (provider == null) {
+ throw new IllegalArgumentException("Widget not bound " + appWidgetId);
+ }
+
+ // Make sure the component refers to the provider config activity.
+ if (!component.equals(provider.info.configure)
+ || !provider.info.getProfile().equals(userHandle)) {
+ throw new IllegalArgumentException("No component" + component
+ + " for user " + userHandle.getIdentifier());
+ }
+
+ // All right, create the sender.
+ final long identity = Binder.clearCallingIdentity();
try {
- p.broadcast.cancel();
+ return PendingIntent.getActivityAsUser(
+ mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_CANCEL_CURRENT, null, userHandle)
+ .getIntentSender();
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(identity);
}
- p.broadcast = null;
}
}
- private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider, Bundle options) {
- if (DBG) log("bindAppWidgetIdImpl appwid=" + appWidgetId
- + " provider=" + provider);
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- options = cloneIfLocalBinder(options);
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id == null) {
- throw new IllegalArgumentException("bad appWidgetId");
- }
- if (id.provider != null) {
- throw new IllegalArgumentException("appWidgetId " + appWidgetId
- + " already bound to " + id.provider.info.provider);
- }
- Provider p = lookupProviderLocked(provider);
- if (p == null) {
- throw new IllegalArgumentException("not a appwidget provider: " + provider);
- }
- if (p.zombie) {
- throw new IllegalArgumentException("can't bind to a 3rd party provider in"
- + " safe mode: " + provider);
- }
+ @Override
+ public boolean bindAppWidgetId(String callingPackage, int appWidgetId,
+ int providerProfileId, ComponentName providerComponent, Bundle options) {
+ final int userId = UserHandle.getCallingUserId();
- id.provider = p;
- if (options == null) {
- options = new Bundle();
- }
- id.options = options;
+ if (DEBUG) {
+ Slog.i(TAG, "bindAppWidgetId() " + userId);
+ }
- // We need to provide a default value for the widget category if it is not specified
- if (!options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
- AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
- }
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
- p.instances.add(id);
- int instancesSize = p.instances.size();
- if (instancesSize == 1) {
- // tell the provider that it's ready
- sendEnableIntentLocked(p);
- }
+ // Check that if a cross-profile binding is attempted, it is allowed.
+ final int[] profileIds = mSecurityPolicy.resolveCallerEnabledGroupProfiles(
+ new int[] {providerProfileId});
+ if (profileIds.length <= 0) {
+ return false;
+ }
- // send an update now -- We need this update now, and just for this appWidgetId.
- // It's less critical when the next one happens, so when we schedule the next one,
- // we add updatePeriodMillis to its start time. That time will have some slop,
- // but that's okay.
- sendUpdateIntentLocked(p, new int[] { appWidgetId });
+ // If the provider is not under the calling user, make sure this
+ // provider is white listed for access from the parent.
+ if (!mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ providerComponent.getPackageName(), providerProfileId)) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
- // schedule the future updates
- registerForBroadcastsLocked(p, getAppWidgetIds(p));
- saveStateAsync();
+ // A special permission or white listing is required to bind widgets.
+ if (!mSecurityPolicy.hasCallerBindPermissionOrBindWhiteListedLocked(
+ callingPackage)) {
+ return false;
}
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET,
- "bindAppWidgetId appWidgetId=" + appWidgetId + " provider=" + provider);
- bindAppWidgetIdImpl(appWidgetId, provider, options);
- }
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
- public boolean bindAppWidgetIdIfAllowed(
- String packageName, int appWidgetId, ComponentName provider, Bundle options) {
- if (!mHasFeature) {
- return false;
- }
- try {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET, null);
- } catch (SecurityException se) {
- if (!callerHasBindAppWidgetPermission(packageName)) {
+ if (widget == null) {
+ Slog.e(TAG, "Bad widget id " + appWidgetId);
return false;
}
- }
- bindAppWidgetIdImpl(appWidgetId, provider, options);
- return true;
- }
- private boolean callerHasBindAppWidgetPermission(String packageName) {
- int callingUid = Binder.getCallingUid();
- try {
- if (!UserHandle.isSameApp(callingUid, getUidForPackage(packageName))) {
+ if (widget.provider != null) {
+ Slog.e(TAG, "Widget id " + appWidgetId
+ + " already bound to: " + widget.provider.id);
return false;
}
- } catch (Exception e) {
- return false;
- }
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- return mPackagesWithBindWidgetPermission.contains(packageName);
+
+ final int providerUid = getUidForPackage(providerComponent.getPackageName(),
+ providerProfileId);
+ if (providerUid < 0) {
+ Slog.e(TAG, "Package " + providerComponent.getPackageName() + " not installed "
+ + " for profile " + providerProfileId);
+ return false;
+ }
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the provider is in the already vetted user profile.
+ ProviderId providerId = new ProviderId(providerUid, providerComponent);
+ Provider provider = lookupProviderLocked(providerId);
+
+ if (provider == null) {
+ Slog.e(TAG, "No widget provider " + providerComponent + " for profile "
+ + providerProfileId);
+ return false;
+ }
+
+ if (provider.zombie) {
+ Slog.e(TAG, "Can't bind to a 3rd party provider in"
+ + " safe mode " + provider);
+ return false;
+ }
+
+ widget.provider = provider;
+ widget.options = (options != null) ? cloneIfLocalBinder(options) : new Bundle();
+
+ // We need to provide a default value for the widget category if it is not specified
+ if (!widget.options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
+ widget.options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
+ }
+
+ provider.widgets.add(widget);
+
+ final int widgetCount = provider.widgets.size();
+ if (widgetCount == 1) {
+ // Tell the provider that it's ready.
+ sendEnableIntentLocked(provider);
+ }
+
+ // Send an update now -- We need this update now, and just for this appWidgetId.
+ // It's less critical when the next one happens, so when we schedule the next one,
+ // we add updatePeriodMillis to its start time. That time will have some slop,
+ // but that's okay.
+ sendUpdateIntentLocked(provider, new int[] {appWidgetId});
+
+ // Schedule the future updates.
+ registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets));
+
+ saveGroupStateAsync(userId);
+
+ if (DEBUG) {
+ Slog.i(TAG, "Bound widget " + appWidgetId + " to provider " + provider.id);
+ }
}
+
+ return true;
}
- public boolean hasBindAppWidgetPermission(String packageName) {
- if (!mHasFeature) {
- return false;
+ @Override
+ public int[] getAppWidgetIds(ComponentName componentName) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "getAppWidgetIds() " + userId);
}
- mContext.enforceCallingPermission(
- android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
- "hasBindAppWidgetPermission packageName=" + packageName);
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- return mPackagesWithBindWidgetPermission.contains(packageName);
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(componentName.getPackageName());
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can access only its providers.
+ ProviderId providerId = new ProviderId(Binder.getCallingUid(), componentName);
+ Provider provider = lookupProviderLocked(providerId);
+
+ if (provider != null) {
+ return getWidgetIds(provider.widgets);
+ }
+
+ return new int[0];
}
}
- public void setBindAppWidgetPermission(String packageName, boolean permission) {
- if (!mHasFeature) {
- return;
+ @Override
+ public int[] getAppWidgetIdsForHost(String callingPackage, int hostId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "getAppWidgetIdsForHost() " + userId);
}
- mContext.enforceCallingPermission(
- android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
- "setBindAppWidgetPermission packageName=" + packageName);
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- if (permission) {
- mPackagesWithBindWidgetPermission.add(packageName);
- } else {
- mPackagesWithBindWidgetPermission.remove(packageName);
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access its hosts.
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupHostLocked(id);
+
+ if (host != null) {
+ return getWidgetIds(host.widgets);
}
- saveStateAsync();
+
+ return new int[0];
}
}
- // Binds to a specific RemoteViewsService
- public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id == null) {
- throw new IllegalArgumentException("bad appWidgetId");
+ @Override
+ public void bindRemoteViewsService(String callingPackage, int appWidgetId,
+ Intent intent, IBinder callbacks) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "bindRemoteViewsService() " + userId);
+ }
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
+ throw new IllegalArgumentException("Bad widget id");
}
- final ComponentName componentName = intent.getComponent();
- try {
- final ServiceInfo si = mPm.getServiceInfo(componentName,
- PackageManager.GET_PERMISSIONS, mUserId);
- if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) {
- throw new SecurityException("Selected service does not require "
- + android.Manifest.permission.BIND_REMOTEVIEWS + ": " + componentName);
- }
- } catch (RemoteException e) {
- throw new IllegalArgumentException("Unknown component " + componentName);
+
+ // Make sure the widget has a provider.
+ if (widget.provider == null) {
+ throw new IllegalArgumentException("No provider for widget "
+ + appWidgetId);
}
- // Ensure that the service specified by the passed intent belongs to the same package
- // as provides the passed widget id.
- String widgetIdPackage = id.provider.info.provider.getPackageName();
+ ComponentName componentName = intent.getComponent();
+
+ // Ensure that the service belongs to the same package as the provider.
+ // But this is not enough as they may be under different users - see below...
+ String providerPackage = widget.provider.id.componentName.getPackageName();
String servicePackage = componentName.getPackageName();
- if (!servicePackage.equals(widgetIdPackage)) {
- throw new SecurityException("Specified intent doesn't belong to the same package"
- + " as the provided AppWidget id");
+ if (!servicePackage.equals(providerPackage)) {
+ throw new SecurityException("The taget service not in the same package"
+ + " as the widget provider");
}
- // If there is already a connection made for this service intent, then disconnect from
- // that first. (This does not allow multiple connections to the same service under
- // the same key)
- ServiceConnectionProxy conn = null;
+ // Make sure this service exists under the same user as the provider and
+ // requires a permission which allows only the system to bind to it.
+ mSecurityPolicy.enforceServiceExistsAndRequiresBindRemoteViewsPermission(
+ componentName, widget.provider.getUserId());
+
+ // Good to go - the service pakcage is correct, it exists for the correct
+ // user, and requires the bind permission.
+
+ // If there is already a connection made for this service intent, then
+ // disconnect from that first. (This does not allow multiple connections
+ // to the same service under the same key).
+ ServiceConnectionProxy connection = null;
FilterComparison fc = new FilterComparison(intent);
Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc);
+
if (mBoundRemoteViewsServices.containsKey(key)) {
- conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
- conn.disconnect();
- mContext.unbindService(conn);
+ connection = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
+ connection.disconnect();
+ unbindService(connection);
mBoundRemoteViewsServices.remove(key);
}
- int userId = UserHandle.getUserId(id.provider.uid);
- if (userId != mUserId) {
- Slog.w(TAG, "AppWidgetServiceImpl of user " + mUserId
- + " binding to provider on user " + userId);
- }
// Bind to the RemoteViewsService (which will trigger a callback to the
// RemoteViewsAdapter.onServiceConnected())
- final long token = Binder.clearCallingIdentity();
- try {
- conn = new ServiceConnectionProxy(key, connection);
- mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
- new UserHandle(userId));
- mBoundRemoteViewsServices.put(key, conn);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ connection = new ServiceConnectionProxy(callbacks);
+ bindService(intent, connection, widget.provider.info.getProfile());
+ mBoundRemoteViewsServices.put(key, connection);
- // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine
- // when we can call back to the RemoteViewsService later to destroy associated
- // factories.
- incrementAppWidgetServiceRefCount(appWidgetId, fc);
+ // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
+ // can determine when we can call back to the RemoteViewsService later to
+ // destroy associated factories.
+ Pair<Integer, FilterComparison> serviceId = Pair.create(widget.provider.id.uid, fc);
+ incrementAppWidgetServiceRefCount(appWidgetId, serviceId);
}
}
- // Unbinds from a specific RemoteViewsService
- public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- ensureStateLoadedLocked();
+ @Override
+ public void unbindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "unbindRemoteViewsService() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
// Unbind from the RemoteViewsService (which will trigger a callback to the bound
// RemoteViewsAdapter)
- Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, new FilterComparison(
- intent));
+ Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
+ new FilterComparison(intent));
if (mBoundRemoteViewsServices.containsKey(key)) {
// We don't need to use the appWidgetId until after we are sure there is something
// to unbind. Note that this may mask certain issues with apps calling unbind()
// more than necessary.
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id == null) {
- throw new IllegalArgumentException("bad appWidgetId");
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
+ throw new IllegalArgumentException("Bad widget id " + appWidgetId);
}
- ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices
- .get(key);
- conn.disconnect();
- mContext.unbindService(conn);
+ ServiceConnectionProxy connection = (ServiceConnectionProxy)
+ mBoundRemoteViewsServices.get(key);
+ connection.disconnect();
+ mContext.unbindService(connection);
mBoundRemoteViewsServices.remove(key);
}
}
}
- // Unbinds from a RemoteViewsService when we delete an app widget
- private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
- int appWidgetId = id.appWidgetId;
- // Unbind all connections to Services bound to this AppWidgetId
- Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet()
- .iterator();
- while (it.hasNext()) {
- final Pair<Integer, Intent.FilterComparison> key = it.next();
- if (key.first.intValue() == appWidgetId) {
- final ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices
- .get(key);
- conn.disconnect();
- mContext.unbindService(conn);
- it.remove();
- }
+ @Override
+ public void deleteHost(String callingPackage, int hostId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "deleteHost() " + userId);
}
- // Check if we need to destroy any services (if no other app widgets are
- // referencing the same service)
- decrementAppWidgetServiceRefCount(id);
- }
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
- // Destroys the cached factory on the RemoteViewsService's side related to the specified intent
- private void destroyRemoteViewsService(final Intent intent, AppWidgetId id) {
- final ServiceConnection conn = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- final IRemoteViewsFactory cb = IRemoteViewsFactory.Stub.asInterface(service);
- try {
- cb.onDestroy(intent);
- } catch (RemoteException e) {
- e.printStackTrace();
- } catch (RuntimeException e) {
- e.printStackTrace();
- }
- mContext.unbindService(this);
- }
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
- @Override
- public void onServiceDisconnected(android.content.ComponentName name) {
- // Do nothing
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access hosts in its uid and package.
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupHostLocked(id);
+
+ if (host == null) {
+ return;
}
- };
- int userId = UserHandle.getUserId(id.provider.uid);
- // Bind to the service and remove the static intent->factory mapping in the
- // RemoteViewsService.
- final long token = Binder.clearCallingIdentity();
- try {
- mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
- new UserHandle(userId));
- } finally {
- Binder.restoreCallingIdentity(token);
+ deleteHostLocked(host);
+
+ saveGroupStateAsync(userId);
+
+ if (DEBUG) {
+ Slog.i(TAG, "Deleted host " + host.id);
+ }
}
}
- // Adds to the ref-count for a given RemoteViewsService intent
- private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) {
- HashSet<Integer> appWidgetIds = null;
- if (mRemoteViewsServicesAppWidgets.containsKey(fc)) {
- appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc);
- } else {
- appWidgetIds = new HashSet<Integer>();
- mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds);
+ @Override
+ public void deleteAllHosts() {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "deleteAllHosts() " + userId);
}
- appWidgetIds.add(appWidgetId);
- }
- // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if
- // the ref-count reaches zero.
- private void decrementAppWidgetServiceRefCount(AppWidgetId id) {
- Iterator<FilterComparison> it = mRemoteViewsServicesAppWidgets.keySet().iterator();
- while (it.hasNext()) {
- final FilterComparison key = it.next();
- final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key);
- if (ids.remove(id.appWidgetId)) {
- // If we have removed the last app widget referencing this service, then we
- // should destroy it and remove it from this set
- if (ids.isEmpty()) {
- destroyRemoteViewsService(key.getIntent(), id);
- it.remove();
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ boolean changed = false;
+
+ final int N = mHosts.size();
+ for (int i = N - 1; i >= 0; i--) {
+ Host host = mHosts.get(i);
+
+ // Delete only hosts in the calling uid.
+ if (host.id.uid == Binder.getCallingUid()) {
+ deleteHostLocked(host);
+ changed = true;
+
+ if (DEBUG) {
+ Slog.i(TAG, "Deleted host " + host.id);
+ }
}
}
+
+ if (changed) {
+ saveGroupStateAsync(userId);
+ }
}
}
- public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return null;
- }
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id != null && id.provider != null && !id.provider.zombie) {
- return cloneIfLocalBinder(id.provider.info);
+ @Override
+ public AppWidgetProviderInfo getAppWidgetInfo(String callingPackage, int appWidgetId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "getAppWidgetInfo() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget != null && widget.provider != null && !widget.provider.zombie) {
+ return cloneIfLocalBinder(widget.provider.info);
}
+
return null;
}
}
- public RemoteViews getAppWidgetViews(int appWidgetId) {
- if (DBG) log("getAppWidgetViews id=" + appWidgetId);
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return null;
- }
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id != null) {
- return cloneIfLocalBinder(id.views);
+ @Override
+ public RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "getAppWidgetViews() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget != null) {
+ return cloneIfLocalBinder(widget.views);
}
- if (DBG) log(" couldn't find appwidgetid");
+
return null;
}
}
- public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return new ArrayList<AppWidgetProviderInfo>(0);
- }
- ensureStateLoadedLocked();
- final int N = mInstalledProviders.size();
- ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N);
- for (int i = 0; i < N; i++) {
- Provider p = mInstalledProviders.get(i);
- if (!p.zombie && (p.info.widgetCategory & categoryFilter) != 0) {
- result.add(cloneIfLocalBinder(p.info));
- }
+ @Override
+ public void updateAppWidgetOptions(String callingPackage, int appWidgetId, Bundle options) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "updateAppWidgetOptions() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
+ return;
}
- return result;
+
+ // Merge the options.
+ widget.options.putAll(options);
+
+ // Send the broacast to notify the provider that options changed.
+ sendOptionsChangedIntentLocked(widget);
+
+ saveGroupStateAsync(userId);
}
}
- public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
- if (!mHasFeature) {
- return;
+ @Override
+ public Bundle getAppWidgetOptions(String callingPackage, int appWidgetId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "getAppWidgetOptions() " + userId);
}
- if (appWidgetIds == null) {
- return;
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget != null && widget.options != null) {
+ return cloneIfLocalBinder(widget.options);
+ }
+
+ return Bundle.EMPTY;
}
- if (DBG) log("updateAppWidgetIds views: " + views);
- int bitmapMemoryUsage = 0;
- if (views != null) {
- bitmapMemoryUsage = views.estimateMemoryUsage();
+ }
+
+ @Override
+ public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
+ RemoteViews views) {
+ if (DEBUG) {
+ Slog.i(TAG, "updateAppWidgetIds() " + UserHandle.getCallingUserId());
}
- if (bitmapMemoryUsage > mMaxWidgetBitmapMemory) {
- throw new IllegalArgumentException("RemoteViews for widget update exceeds maximum" +
- " bitmap memory usage (used: " + bitmapMemoryUsage + ", max: " +
- mMaxWidgetBitmapMemory + ") The total memory cannot exceed that required to" +
- " fill the device's screen once.");
+
+ updateAppWidgetIds(callingPackage, appWidgetIds, views, false);
+ }
+
+ @Override
+ public void partiallyUpdateAppWidgetIds(String callingPackage, int[] appWidgetIds,
+ RemoteViews views) {
+ if (DEBUG) {
+ Slog.i(TAG, "partiallyUpdateAppWidgetIds() " + UserHandle.getCallingUserId());
}
- if (appWidgetIds.length == 0) {
+ updateAppWidgetIds(callingPackage, appWidgetIds, views, true);
+ }
+
+ @Override
+ public void notifyAppWidgetViewDataChanged(String callingPackage, int[] appWidgetIds,
+ int viewId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "notifyAppWidgetViewDataChanged() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ if (appWidgetIds == null || appWidgetIds.length == 0) {
return;
}
- final int N = appWidgetIds.length;
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
- updateAppWidgetInstanceLocked(id, views);
+ final int appWidgetId = appWidgetIds[i];
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget != null) {
+ scheduleNotifyAppWidgetViewDataChanged(widget, viewId);
+ }
}
}
}
- private void saveStateAsync() {
- mSaveStateHandler.post(mSaveStateRunnable);
- }
+ @Override
+ public void updateAppWidgetProvider(ComponentName componentName, RemoteViews views) {
+ final int userId = UserHandle.getCallingUserId();
- private final Runnable mSaveStateRunnable = new Runnable() {
- @Override
- public void run() {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- saveStateLocked();
- }
+ if (DEBUG) {
+ Slog.i(TAG, "updateAppWidgetProvider() " + userId);
}
- };
- public void updateAppWidgetOptions(int appWidgetId, Bundle options) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(componentName.getPackageName());
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can access only its providers.
+ ProviderId providerId = new ProviderId(Binder.getCallingUid(), componentName);
+ Provider provider = lookupProviderLocked(providerId);
+
+ if (provider == null) {
+ Slog.w(TAG, "Provider doesn't exist " + providerId);
return;
}
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id == null) {
- return;
+ ArrayList<Widget> instances = provider.widgets;
+ final int N = instances.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = instances.get(i);
+ updateAppWidgetInstanceLocked(widget, views, false);
}
+ }
+ }
- Provider p = id.provider;
- // Merge the options
- id.options.putAll(cloneIfLocalBinder(options));
+ @Override
+ public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int[] profileIds) {
+ final int userId = UserHandle.getCallingUserId();
- // send the broacast saying that this appWidgetId has been deleted
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED);
- intent.setComponent(p.info.provider);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, id.options);
- mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
- saveStateAsync();
+ if (DEBUG) {
+ Slog.i(TAG, "getInstalledProvidersForProfiles() " + userId);
}
- }
- public Bundle getAppWidgetOptions(int appWidgetId) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return Bundle.EMPTY;
- }
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id != null && id.options != null) {
- return cloneIfLocalBinder(id.options);
- } else {
- return Bundle.EMPTY;
+ if (profileIds != null && profileIds.length > 0) {
+ // Make sure the profile ids are children of the calling user.
+ profileIds = mSecurityPolicy.resolveCallerEnabledGroupProfiles(profileIds);
+ } else {
+ profileIds = new int[] {userId};
+ }
+
+ if (profileIds.length == 0) {
+ return null;
+ }
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ ArrayList<AppWidgetProviderInfo> result = new ArrayList<>();
+
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ AppWidgetProviderInfo info = provider.info;
+
+ // Ignore an invalid provider or one not matching the filter.
+ if (provider.zombie || (info.widgetCategory & categoryFilter) == 0) {
+ continue;
+ }
+
+ // Add providers only for the requested profiles ...
+ final int providerProfileId = info.getProfile().getIdentifier();
+ final int profileCount = profileIds.length;
+ for (int j = 0; j < profileCount; j++) {
+ final int profileId = profileIds[j];
+ if (providerProfileId == profileId) {
+ // ... that are white-listed by the profile manager.
+ if (mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ provider.id.componentName.getPackageName(), providerProfileId)) {
+ result.add(cloneIfLocalBinder(info));
+ }
+ break;
+ }
+ }
}
+
+ return result;
}
}
- public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
- if (!mHasFeature) {
- return;
- }
- if (appWidgetIds == null) {
+ private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
+ RemoteViews views, boolean partially) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (appWidgetIds == null || appWidgetIds.length == 0) {
return;
}
- if (appWidgetIds.length == 0) {
- return;
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ final int bitmapMemoryUsage = (views != null) ? views.estimateMemoryUsage() : 0;
+ if (bitmapMemoryUsage > mMaxWidgetBitmapMemory) {
+ throw new IllegalArgumentException("RemoteViews for widget update exceeds"
+ + " maximum bitmap memory usage (used: " + bitmapMemoryUsage
+ + ", max: " + mMaxWidgetBitmapMemory + ")");
}
- final int N = appWidgetIds.length;
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
- if (id == null) {
- Slog.w(TAG, "widget id " + appWidgetIds[i] + " not found!");
- } else if (id.views != null) {
- // Only trigger a partial update for a widget if it has received a full update
- updateAppWidgetInstanceLocked(id, views, true);
+ final int appWidgetId = appWidgetIds[i];
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget != null) {
+ updateAppWidgetInstanceLocked(widget, views, partially);
}
}
}
}
- public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
- if (!mHasFeature) {
- return;
- }
- if (appWidgetIds == null) {
- return;
+ private int incrementAndGetAppWidgetIdLocked(int userId) {
+ final int appWidgetId = peekNextAppWidgetIdLocked(userId) + 1;
+ mNextAppWidgetIds.put(userId, appWidgetId);
+ return appWidgetId;
+ }
+
+ private void setMinAppWidgetIdLocked(int userId, int minWidgetId) {
+ final int nextAppWidgetId = peekNextAppWidgetIdLocked(userId);
+ if (nextAppWidgetId < minWidgetId) {
+ mNextAppWidgetIds.put(userId, minWidgetId);
}
- if (appWidgetIds.length == 0) {
- return;
+ }
+
+ private int peekNextAppWidgetIdLocked(int userId) {
+ if (mNextAppWidgetIds.indexOfKey(userId) < 0) {
+ return AppWidgetManager.INVALID_APPWIDGET_ID + 1;
+ } else {
+ return mNextAppWidgetIds.get(userId);
}
- final int N = appWidgetIds.length;
+ }
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
- notifyAppWidgetViewDataChangedInstanceLocked(id, viewId);
- }
+ private Host lookupOrAddHostLocked(HostId id) {
+ Host host = lookupHostLocked(id);
+ if (host != null) {
+ return host;
}
+
+ host = new Host();
+ host.id = id;
+ mHosts.add(host);
+
+ return host;
}
- public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) {
- if (!mHasFeature) {
- return;
+ private void deleteHostLocked(Host host) {
+ final int N = host.widgets.size();
+ for (int i = N - 1; i >= 0; i--) {
+ Widget widget = host.widgets.remove(i);
+ deleteAppWidgetLocked(widget);
}
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- Provider p = lookupProviderLocked(provider);
- if (p == null) {
- Slog.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider);
- return;
- }
- ArrayList<AppWidgetId> instances = p.instances;
- final int callingUid = Binder.getCallingUid();
- final int N = instances.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = instances.get(i);
- if (canAccessAppWidgetId(id, callingUid)) {
- updateAppWidgetInstanceLocked(id, views);
+ mHosts.remove(host);
+
+ // it's gone or going away, abruptly drop the callback connection
+ host.callbacks = null;
+ }
+
+ private void deleteAppWidgetLocked(Widget widget) {
+ // We first unbind all services that are bound to this id
+ unbindAppWidgetRemoteViewsServicesLocked(widget);
+
+ Host host = widget.host;
+ host.widgets.remove(widget);
+ pruneHostLocked(host);
+
+ mWidgets.remove(widget);
+
+ Provider provider = widget.provider;
+ if (provider != null) {
+ provider.widgets.remove(widget);
+ if (!provider.zombie) {
+ // send the broacast saying that this appWidgetId has been deleted
+ sendDeletedIntentLocked(widget);
+
+ if (provider.widgets.isEmpty()) {
+ // cancel the future updates
+ cancelBroadcasts(provider);
+
+ // send the broacast saying that the provider is not in use any more
+ sendDisabledIntentLocked(provider);
}
}
}
}
- void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) {
- updateAppWidgetInstanceLocked(id, views, false);
+ private void cancelBroadcasts(Provider provider) {
+ if (DEBUG) {
+ Slog.i(TAG, "cancelBroadcasts() for " + provider);
+ }
+ if (provider.broadcast != null) {
+ mAlarmManager.cancel(provider.broadcast);
+ long token = Binder.clearCallingIdentity();
+ try {
+ provider.broadcast.cancel();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ provider.broadcast = null;
+ }
}
- 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) {
-
- if (!isPartialUpdate) {
- // For a full update we replace the RemoteViews completely.
- id.views = views;
- } else {
- // For a partial update, we merge the new RemoteViews with the old.
- id.views.mergeRemoteViews(views);
+ // Unbinds from a RemoteViewsService when we delete an app widget
+ private void unbindAppWidgetRemoteViewsServicesLocked(Widget widget) {
+ int appWidgetId = widget.appWidgetId;
+ // Unbind all connections to Services bound to this AppWidgetId
+ Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet()
+ .iterator();
+ while (it.hasNext()) {
+ final Pair<Integer, Intent.FilterComparison> key = it.next();
+ if (key.first == appWidgetId) {
+ final ServiceConnectionProxy conn = (ServiceConnectionProxy)
+ mBoundRemoteViewsServices.get(key);
+ conn.disconnect();
+ mContext.unbindService(conn);
+ it.remove();
}
+ }
- // is anyone listening?
- if (id.host.callbacks != null) {
+ // Check if we need to destroy any services (if no other app widgets are
+ // referencing the same service)
+ decrementAppWidgetServiceRefCount(widget);
+ }
+
+ // Destroys the cached factory on the RemoteViewsService's side related to the specified intent
+ private void destroyRemoteViewsService(final Intent intent, Widget widget) {
+ final ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ final IRemoteViewsFactory cb = IRemoteViewsFactory.Stub.asInterface(service);
try {
- // the lock is held, but this is a oneway call
- id.host.callbacks.updateAppWidget(id.appWidgetId, views, mUserId);
- } 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;
+ cb.onDestroy(intent);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error calling remove view factory", re);
}
+ mContext.unbindService(this);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // Do nothing
}
+ };
+
+ // Bind to the service and remove the static intent->factory mapping in the
+ // RemoteViewsService.
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
+ widget.provider.info.getProfile());
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
- 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, mUserId);
- } 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;
+ // Adds to the ref-count for a given RemoteViewsService intent
+ private void incrementAppWidgetServiceRefCount(int appWidgetId,
+ Pair<Integer, FilterComparison> serviceId) {
+ HashSet<Integer> appWidgetIds = null;
+ if (mRemoteViewsServicesAppWidgets.containsKey(serviceId)) {
+ appWidgetIds = mRemoteViewsServicesAppWidgets.get(serviceId);
+ } else {
+ appWidgetIds = new HashSet<>();
+ mRemoteViewsServicesAppWidgets.put(serviceId, appWidgetIds);
+ }
+ appWidgetIds.add(appWidgetId);
+ }
+
+ // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if
+ // the ref-count reaches zero.
+ private void decrementAppWidgetServiceRefCount(Widget widget) {
+ Iterator<Pair<Integer, FilterComparison>> it = mRemoteViewsServicesAppWidgets
+ .keySet().iterator();
+ while (it.hasNext()) {
+ final Pair<Integer, FilterComparison> key = it.next();
+ final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key);
+ if (ids.remove(widget.appWidgetId)) {
+ // If we have removed the last app widget referencing this service, then we
+ // should destroy it and remove it from this set
+ if (ids.isEmpty()) {
+ destroyRemoteViewsService(key.second.getIntent(), widget);
+ it.remove();
}
}
+ }
+ }
+
+ private void saveGroupStateAsync(int groupId) {
+ mSaveStateHandler.post(new SaveStateRunnable(groupId));
+ }
+
+ private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
+ boolean isPartialUpdate) {
+ if (widget != null && widget.provider != null
+ && !widget.provider.zombie && !widget.host.zombie) {
+
+ if (isPartialUpdate && widget.views != null) {
+ // For a partial update, we merge the new RemoteViews with the old.
+ widget.views.mergeRemoteViews(views);
+ } else {
+ // For a full update we replace the RemoteViews completely.
+ widget.views = views;
+ }
+
+ scheduleNotifyUpdateAppWidgetLocked(widget);
+ }
+ }
+
+ private void scheduleNotifyAppWidgetViewDataChanged(Widget widget, int viewId) {
+ if (widget == null || widget.host == null || widget.host.zombie
+ || widget.host.callbacks == null || widget.provider == null
+ || widget.provider.zombie) {
+ return;
+ }
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = widget.host;
+ args.arg2 = widget.host.callbacks;
+ args.argi1 = widget.appWidgetId;
+ args.argi2 = viewId;
+
+ mCallbackHandler.obtainMessage(
+ CallbackHandler.MSG_NOTIFY_VIEW_DATA_CHANGED,
+ args).sendToTarget();
+ }
+
+
+ private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
+ int appWidgetId, int viewId) {
+ try {
+ callbacks.viewDataChanged(appWidgetId, viewId);
+ } catch (RemoteException re) {
+ // It failed; remove the callback. No need to prune because
+ // we know that this host is still referenced by this instance.
+ callbacks = null;
+ }
- // If the host is unavailable, then we call the associated
- // RemoteViewsFactory.onDataSetChanged() directly
- if (id.host.callbacks == null) {
- Set<FilterComparison> keys = mRemoteViewsServicesAppWidgets.keySet();
- for (FilterComparison key : keys) {
- if (mRemoteViewsServicesAppWidgets.get(key).contains(id.appWidgetId)) {
- Intent intent = key.getIntent();
+ // If the host is unavailable, then we call the associated
+ // RemoteViewsFactory.onDataSetChanged() directly
+ synchronized (mLock) {
+ if (callbacks == null) {
+ host.callbacks = null;
- final ServiceConnection conn = new ServiceConnection() {
+ Set<Pair<Integer, FilterComparison>> keys = mRemoteViewsServicesAppWidgets.keySet();
+ for (Pair<Integer, FilterComparison> key : keys) {
+ if (mRemoteViewsServicesAppWidgets.get(key).contains(appWidgetId)) {
+ final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IRemoteViewsFactory cb = IRemoteViewsFactory.Stub
@@ -1275,9 +1641,7 @@ class AppWidgetServiceImpl {
try {
cb.onDataSetChangedAsync();
} catch (RemoteException e) {
- e.printStackTrace();
- } catch (RuntimeException e) {
- e.printStackTrace();
+ Slog.e(TAG, "Error calling onDataSetChangedAsync()", e);
}
mContext.unbindService(this);
}
@@ -1288,875 +1652,453 @@ class AppWidgetServiceImpl {
}
};
- int userId = UserHandle.getUserId(id.provider.uid);
+ final int userId = UserHandle.getUserId(key.first);
+ Intent intent = key.second.getIntent();
+
// Bind to the service and call onDataSetChanged()
- final long token = Binder.clearCallingIdentity();
- try {
- mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
- new UserHandle(userId));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ bindService(intent, connection, new UserHandle(userId));
}
}
}
}
}
- private boolean isLocalBinder() {
- return Process.myPid() == Binder.getCallingPid();
- }
-
- private RemoteViews cloneIfLocalBinder(RemoteViews rv) {
- if (isLocalBinder() && rv != null) {
- return rv.clone();
+ private void scheduleNotifyUpdateAppWidgetLocked(Widget widget) {
+ if (widget == null || widget.provider == null || widget.provider.zombie
+ || widget.host.callbacks == null || widget.host.zombie) {
+ return;
}
- return rv;
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = widget.host;
+ args.arg2 = widget.host.callbacks;
+ args.arg3 = widget.views;
+ args.argi1 = widget.appWidgetId;
+
+ mCallbackHandler.obtainMessage(
+ CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,
+ args).sendToTarget();
}
- private AppWidgetProviderInfo cloneIfLocalBinder(AppWidgetProviderInfo info) {
- if (isLocalBinder() && info != null) {
- return info.clone();
+ private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
+ int appWidgetId, RemoteViews views) {
+ try {
+ callbacks.updateAppWidget(appWidgetId, views);
+ } catch (RemoteException re) {
+ synchronized (mLock) {
+ Slog.e(TAG, "Widget host dead: " + host.id, re);
+ host.callbacks = null;
+ }
}
- return info;
}
- private Bundle cloneIfLocalBinder(Bundle bundle) {
- // Note: this is only a shallow copy. For now this will be fine, but it could be problematic
- // if we start adding objects to the options. Further, it would only be an issue if keyguard
- // used such options.
- if (isLocalBinder() && bundle != null) {
- return (Bundle) bundle.clone();
+ private void scheduleNotifyProviderChangedLocked(Widget widget) {
+ if (widget == null || widget.provider == null || widget.provider.zombie
+ || widget.host.callbacks == null || widget.host.zombie) {
+ return;
}
- return bundle;
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = widget.host;
+ args.arg2 = widget.host.callbacks;
+ args.arg3 = widget.provider.info;
+ args.argi1 = widget.appWidgetId;
+
+ mCallbackHandler.obtainMessage(
+ CallbackHandler.MSG_NOTIFY_PROVIDER_CHANGED,
+ args).sendToTarget();
}
- public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
- List<RemoteViews> updatedViews) {
- if (!mHasFeature) {
- return new int[0];
+ private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
+ int appWidgetId, AppWidgetProviderInfo info) {
+ try {
+ callbacks.providerChanged(appWidgetId, info);
+ } catch (RemoteException re) {
+ synchronized (mLock){
+ Slog.e(TAG, "Widget host dead: " + host.id, re);
+ host.callbacks = null;
+ }
}
- int callingUid = enforceCallingUid(packageName);
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
- host.callbacks = callbacks;
+ }
- updatedViews.clear();
+ private void scheduleNotifyHostsForProvidersChangedLocked() {
+ final int N = mHosts.size();
+ for (int i = N - 1; i >= 0; i--) {
+ Host host = mHosts.get(i);
- ArrayList<AppWidgetId> instances = host.instances;
- int N = instances.size();
- int[] updatedIds = new int[N];
- for (int i = 0; i < N; i++) {
- AppWidgetId id = instances.get(i);
- updatedIds[i] = id.appWidgetId;
- updatedViews.add(cloneIfLocalBinder(id.views));
+ if (host == null || host.zombie || host.callbacks == null) {
+ continue;
}
- return updatedIds;
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = host;
+ args.arg2 = host.callbacks;
+
+ mCallbackHandler.obtainMessage(
+ CallbackHandler.MSG_NOTIFY_PROVIDERS_CHANGED,
+ args).sendToTarget();
}
}
- public void stopListening(int hostId) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- ensureStateLoadedLocked();
- Host host = lookupHostLocked(Binder.getCallingUid(), hostId);
- if (host != null) {
+ private void handleNotifyProvidersChanged(Host host, IAppWidgetHost callbacks) {
+ try {
+ callbacks.providersChanged();
+ } catch (RemoteException re) {
+ synchronized (mLock) {
+ Slog.e(TAG, "Widget host dead: " + host.id, re);
host.callbacks = null;
- pruneHostLocked(host);
}
}
}
- boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) {
- if (id.host.uidMatches(callingUid)) {
- // Apps hosting the AppWidget have access to it.
- return true;
- }
- if (id.provider != null && id.provider.uid == callingUid) {
- // Apps providing the AppWidget have access to it (if the appWidgetId has been bound)
- return true;
- }
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET) == PackageManager.PERMISSION_GRANTED) {
- // Apps that can bind have access to all appWidgetIds.
- return true;
+ private static boolean isLocalBinder() {
+ return Process.myPid() == Binder.getCallingPid();
+ }
+
+ private static RemoteViews cloneIfLocalBinder(RemoteViews rv) {
+ if (isLocalBinder() && rv != null) {
+ return rv.clone();
}
- // Nobody else can access it.
- return false;
+ return rv;
}
- AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) {
- int callingUid = Binder.getCallingUid();
- final int N = mAppWidgetIds.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = mAppWidgetIds.get(i);
- if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) {
- return id;
- }
+ private static AppWidgetProviderInfo cloneIfLocalBinder(AppWidgetProviderInfo info) {
+ if (isLocalBinder() && info != null) {
+ return info.clone();
}
- return null;
+ return info;
}
- Provider lookupProviderLocked(ComponentName provider) {
- return lookupProviderLocked(provider, mInstalledProviders);
+ private static Bundle cloneIfLocalBinder(Bundle bundle) {
+ // Note: this is only a shallow copy. For now this will be fine, but it could be problematic
+ // if we start adding objects to the options. Further, it would only be an issue if keyguard
+ // used such options.
+ if (isLocalBinder() && bundle != null) {
+ return (Bundle) bundle.clone();
+ }
+ return bundle;
}
- Provider lookupProviderLocked(ComponentName provider, ArrayList<Provider> installedProviders) {
- final int N = installedProviders.size();
+ private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {
+ final int N = mWidgets.size();
for (int i = 0; i < N; i++) {
- Provider p = installedProviders.get(i);
- if (p.info.provider.equals(provider)) {
- return p;
+ Widget widget = mWidgets.get(i);
+ if (widget.appWidgetId == appWidgetId
+ && mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) {
+ return widget;
}
}
return null;
}
- Host lookupHostLocked(int uid, int hostId) {
- final int N = mHosts.size();
+ private Provider lookupProviderLocked(ProviderId id) {
+ final int N = mProviders.size();
for (int i = 0; i < N; i++) {
- Host h = mHosts.get(i);
- if (h.uidMatches(uid) && h.hostId == hostId) {
- return h;
+ Provider provider = mProviders.get(i);
+ if (provider.id.equals(id)) {
+ return provider;
}
}
return null;
}
- Host lookupOrAddHostLocked(int uid, String packageName, int hostId) {
+ private Host lookupHostLocked(HostId hostId) {
final int N = mHosts.size();
for (int i = 0; i < N; i++) {
- Host h = mHosts.get(i);
- if (h.hostId == hostId && h.packageName.equals(packageName)) {
- return h;
+ Host host = mHosts.get(i);
+ if (host.id.equals(hostId)) {
+ return host;
}
}
- Host host = new Host();
- host.packageName = packageName;
- host.uid = uid;
- host.hostId = hostId;
- mHosts.add(host);
- return host;
+ return null;
}
- void pruneHostLocked(Host host) {
- if (host.instances.size() == 0 && host.callbacks == null) {
- if (DBG) log("Pruning host " + host);
+ private void pruneHostLocked(Host host) {
+ if (host.widgets.size() == 0 && host.callbacks == null) {
+ if (DEBUG) {
+ Slog.i(TAG, "Pruning host " + host.id);
+ }
mHosts.remove(host);
}
}
- void loadWidgetProviderListLocked() {
+ private void loadGroupWidgetProvidersLocked(int[] profileIds) {
+ List<ResolveInfo> allReceivers = null;
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- try {
- List<ResolveInfo> broadcastReceivers = mPm.queryIntentReceivers(intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.GET_META_DATA, mUserId);
- final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
- for (int i = 0; i < N; i++) {
- ResolveInfo ri = broadcastReceivers.get(i);
- addProviderLocked(ri);
+ final int profileCount = profileIds.length;
+ for (int i = 0; i < profileCount; i++) {
+ final int profileId = profileIds[i];
+
+ List<ResolveInfo> receivers = queryIntentReceivers(intent, profileId);
+ if (receivers != null && !receivers.isEmpty()) {
+ if (allReceivers == null) {
+ allReceivers = new ArrayList<>();
+ }
+ allReceivers.addAll(receivers);
}
- } catch (RemoteException re) {
- // Shouldn't happen, local call
+ }
+
+ final int N = (allReceivers == null) ? 0 : allReceivers.size();
+ for (int i = 0; i < N; i++) {
+ ResolveInfo receiver = allReceivers.get(i);
+ addProviderLocked(receiver);
}
}
- boolean addProviderLocked(ResolveInfo ri) {
+ private 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) {
+
+ ComponentName componentName = new ComponentName(ri.activityInfo.packageName,
+ ri.activityInfo.name);
+ ProviderId providerId = new ProviderId(ri.activityInfo.applicationInfo.uid, componentName);
+
+ Provider provider = parseProviderInfoXml(providerId, ri);
+ if (provider != null) {
// we might have an inactive entry for this provider already due to
// a preceding restore operation. if so, fix it up in place; otherwise
// just add this new one.
- Provider existing = lookupProviderLocked(p.info.provider);
+ Provider existing = lookupProviderLocked(providerId);
+
+ // If the provider was not found it may be because it was restored and
+ // we did not know its UID so let us find if there is such one.
+ if (existing == null) {
+ providerId = new ProviderId(UNKNOWN_UID, componentName);
+ existing = lookupProviderLocked(providerId);
+ }
+
if (existing != null) {
if (existing.zombie && !mSafeMode) {
// it's a placeholder that was set up during an app restore
existing.zombie = false;
- existing.info = p.info; // the real one filled out from the ResolveInfo
- existing.uid = p.uid;
- if (DEBUG_BACKUP) {
+ existing.info = provider.info; // the real one filled out from the ResolveInfo
+ if (DEBUG) {
Slog.i(TAG, "Provider placeholder now reified: " + existing);
}
}
} else {
- mInstalledProviders.add(p);
+ mProviders.add(provider);
}
return true;
- } else {
- return false;
}
+
+ return false;
}
- void removeProviderLocked(int index, Provider p) {
- int N = p.instances.size();
+ private void deleteProviderLocked(Provider provider) {
+ int N = provider.widgets.size();
for (int i = 0; i < N; i++) {
- AppWidgetId id = p.instances.get(i);
+ Widget widget = provider.widgets.remove(i);
// Call back with empty RemoteViews
- updateAppWidgetInstanceLocked(id, null);
- // Stop telling the host about updates for this from now on
- cancelBroadcasts(p);
+ updateAppWidgetInstanceLocked(widget, null, false);
// clear out references to this appWidgetId
- id.host.instances.remove(id);
- mAppWidgetIds.remove(id);
- id.provider = null;
- pruneHostLocked(id.host);
- id.host = null;
- }
- p.instances.clear();
- mInstalledProviders.remove(index);
- mDeletedProviders.add(p);
+ widget.host.widgets.remove(widget);
+ mWidgets.remove(widget);
+ widget.provider = null;
+ pruneHostLocked(widget.host);
+ widget.host = null;
+ }
+ mProviders.remove(provider);
+
// no need to send the DISABLE broadcast, since the receiver is gone anyway
- cancelBroadcasts(p);
+ cancelBroadcasts(provider);
}
- void sendEnableIntentLocked(Provider p) {
+ private void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.info.provider);
- mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
+ sendBroadcastAsUser(intent, p.info.getProfile());
}
- void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) {
- if (appWidgetIds != null && appWidgetIds.length > 0) {
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
- intent.setComponent(p.info.provider);
- mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
- }
+ private void sendUpdateIntentLocked(Provider provider, int[] appWidgetIds) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
+ intent.setComponent(provider.info.provider);
+ sendBroadcastAsUser(intent, provider.info.getProfile());
+ }
+
+ private void sendDeletedIntentLocked(Widget widget) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
+ intent.setComponent(widget.provider.info.provider);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
+ sendBroadcastAsUser(intent, widget.provider.info.getProfile());
+ }
+
+ private void sendDisabledIntentLocked(Provider provider) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
+ intent.setComponent(provider.info.provider);
+ sendBroadcastAsUser(intent, provider.info.getProfile());
}
- void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) {
- if (p.info.updatePeriodMillis > 0) {
+ public void sendOptionsChangedIntentLocked(Widget widget) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED);
+ intent.setComponent(widget.provider.info.provider);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, widget.options);
+ sendBroadcastAsUser(intent, widget.provider.info.getProfile());
+ }
+
+ private void registerForBroadcastsLocked(Provider provider, int[] appWidgetIds) {
+ if (provider.info.updatePeriodMillis > 0) {
// if this is the first instance, set the alarm. otherwise,
// rely on the fact that we've already set it and that
// PendingIntent.getBroadcast will update the extras.
- boolean alreadyRegistered = p.broadcast != null;
+ boolean alreadyRegistered = provider.broadcast != null;
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
- intent.setComponent(p.info.provider);
+ intent.setComponent(provider.info.provider);
long token = Binder.clearCallingIdentity();
try {
- p.broadcast = PendingIntent.getBroadcastAsUser(mContext, 1, intent,
- PendingIntent.FLAG_UPDATE_CURRENT, new UserHandle(mUserId));
+ provider.broadcast = PendingIntent.getBroadcastAsUser(mContext, 1, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT, provider.info.getProfile());
} finally {
Binder.restoreCallingIdentity(token);
}
if (!alreadyRegistered) {
- long period = p.info.updatePeriodMillis;
+ long period = provider.info.updatePeriodMillis;
if (period < MIN_UPDATE_PERIOD) {
period = MIN_UPDATE_PERIOD;
}
- mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock
- .elapsedRealtime()
- + period, period, p.broadcast);
+ mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + period, period, provider.broadcast);
}
}
}
- static int[] getAppWidgetIds(Provider p) {
- int instancesSize = p.instances.size();
+ private static int[] getWidgetIds(ArrayList<Widget> widgets) {
+ int instancesSize = widgets.size();
int appWidgetIds[] = new int[instancesSize];
for (int i = 0; i < instancesSize; i++) {
- appWidgetIds[i] = p.instances.get(i).appWidgetId;
+ appWidgetIds[i] = widgets.get(i).appWidgetId;
}
return appWidgetIds;
}
- public int[] getAppWidgetIds(ComponentName provider) {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- Provider p = lookupProviderLocked(provider);
- if (p != null && Binder.getCallingUid() == p.uid) {
- return getAppWidgetIds(p);
- } else {
- return new int[0];
- }
- }
+ private static void dumpProvider(Provider provider, int index, PrintWriter pw) {
+ AppWidgetProviderInfo info = provider.info;
+ pw.print(" ["); pw.print(index); pw.print("] provider ");
+ pw.println(provider.id);
+ pw.print(" min=("); pw.print(info.minWidth);
+ pw.print("x"); pw.print(info.minHeight);
+ pw.print(") minResize=("); pw.print(info.minResizeWidth);
+ pw.print("x"); pw.print(info.minResizeHeight);
+ pw.print(") updatePeriodMillis=");
+ pw.print(info.updatePeriodMillis);
+ pw.print(" resizeMode=");
+ pw.print(info.resizeMode);
+ pw.print(info.widgetCategory);
+ pw.print(" autoAdvanceViewId=");
+ pw.print(info.autoAdvanceViewId);
+ pw.print(" initialLayout=#");
+ pw.print(Integer.toHexString(info.initialLayout));
+ pw.print(" initialKeyguardLayout=#");
+ pw.print(Integer.toHexString(info.initialKeyguardLayout));
+ pw.print(" zombie="); pw.println(provider.zombie);
+ }
+
+ private static void dumpHost(Host host, int index, PrintWriter pw) {
+ pw.print(" ["); pw.print(index); pw.print("] hostId=");
+ pw.println(host.id);
+ pw.print(" callbacks="); pw.println(host.callbacks);
+ pw.print(" widgets.size="); pw.print(host.widgets.size());
+ pw.print(" zombie="); pw.println(host.zombie);
}
- static int[] getAppWidgetIds(Host h) {
- int instancesSize = h.instances.size();
- int appWidgetIds[] = new int[instancesSize];
- for (int i = 0; i < instancesSize; i++) {
- appWidgetIds[i] = h.instances.get(i).appWidgetId;
- }
- return appWidgetIds;
+ private static void dumpGrant(Pair<Integer, String> grant, int index, PrintWriter pw) {
+ pw.print(" ["); pw.print(index); pw.print(']');
+ pw.print(" user="); pw.print(grant.first);
+ pw.print(" package="); pw.println(grant.second);
}
- public int[] getAppWidgetIdsForHost(int hostId) {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- int callingUid = Binder.getCallingUid();
- Host host = lookupHostLocked(callingUid, hostId);
- if (host != null) {
- return getAppWidgetIds(host);
- } else {
- return new int[0];
- }
+ private static void dumpWidget(Widget widget, int index, PrintWriter pw) {
+ pw.print(" ["); pw.print(index); pw.print("] id=");
+ pw.println(widget.appWidgetId);
+ pw.print(" host=");
+ pw.println(widget.host.id);
+ if (widget.provider != null) {
+ pw.print(" provider="); pw.println(widget.provider.id);
}
- }
-
- public List<String> getWidgetParticipants() {
- HashSet<String> packages = new HashSet<String>();
- synchronized (mAppWidgetIds) {
- final int N = mAppWidgetIds.size();
- for (int i = 0; i < N; i++) {
- final AppWidgetId id = mAppWidgetIds.get(i);
- packages.add(id.host.packageName);
- packages.add(id.provider.info.provider.getPackageName());
- }
+ if (widget.host != null) {
+ pw.print(" host.callbacks="); pw.println(widget.host.callbacks);
+ }
+ if (widget.views != null) {
+ pw.print(" views="); pw.println(widget.views);
}
- return new ArrayList<String>(packages);
}
- private void serializeProvider(XmlSerializer out, Provider p) throws IOException {
+ private static void serializeProvider(XmlSerializer out, Provider p) throws IOException {
out.startTag(null, "p");
out.attribute(null, "pkg", p.info.provider.getPackageName());
out.attribute(null, "cl", p.info.provider.getClassName());
+ out.attribute(null, "tag", Integer.toHexString(p.tag));
out.endTag(null, "p");
}
- private void serializeHost(XmlSerializer out, Host host) throws IOException {
+ private static void serializeHost(XmlSerializer out, Host host) throws IOException {
out.startTag(null, "h");
- out.attribute(null, "pkg", host.packageName);
- out.attribute(null, "id", Integer.toHexString(host.hostId));
+ out.attribute(null, "pkg", host.id.packageName);
+ out.attribute(null, "id", Integer.toHexString(host.id.hostId));
+ out.attribute(null, "tag", Integer.toHexString(host.tag));
out.endTag(null, "h");
}
- private void serializeAppWidgetId(XmlSerializer out, AppWidgetId id) throws IOException {
+ private static void serializeAppWidget(XmlSerializer out, Widget widget) throws IOException {
out.startTag(null, "g");
- out.attribute(null, "id", Integer.toHexString(id.appWidgetId));
- out.attribute(null, "rid", Integer.toHexString(id.restoredId));
- out.attribute(null, "h", Integer.toHexString(id.host.tag));
- if (id.provider != null) {
- out.attribute(null, "p", Integer.toHexString(id.provider.tag));
- }
- if (id.options != null) {
- out.attribute(null, "min_width", Integer.toHexString(id.options.getInt(
+ out.attribute(null, "id", Integer.toHexString(widget.appWidgetId));
+ out.attribute(null, "rid", Integer.toHexString(widget.restoredId));
+ out.attribute(null, "h", Integer.toHexString(widget.host.tag));
+ if (widget.provider != null) {
+ out.attribute(null, "p", Integer.toHexString(widget.provider.tag));
+ }
+ if (widget.options != null) {
+ out.attribute(null, "min_width", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)));
- out.attribute(null, "min_height", Integer.toHexString(id.options.getInt(
+ out.attribute(null, "min_height", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)));
- out.attribute(null, "max_width", Integer.toHexString(id.options.getInt(
+ out.attribute(null, "max_width", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)));
- out.attribute(null, "max_height", Integer.toHexString(id.options.getInt(
+ out.attribute(null, "max_height", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)));
- out.attribute(null, "host_category", Integer.toHexString(id.options.getInt(
+ out.attribute(null, "host_category", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
}
out.endTag(null, "g");
}
- private Bundle parseWidgetIdOptions(XmlPullParser parser) {
- Bundle options = new Bundle();
- String minWidthString = parser.getAttributeValue(null, "min_width");
- if (minWidthString != null) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
- Integer.parseInt(minWidthString, 16));
- }
- String minHeightString = parser.getAttributeValue(null, "min_height");
- if (minHeightString != null) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
- Integer.parseInt(minHeightString, 16));
- }
- String maxWidthString = parser.getAttributeValue(null, "max_width");
- if (maxWidthString != null) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
- Integer.parseInt(maxWidthString, 16));
- }
- String maxHeightString = parser.getAttributeValue(null, "max_height");
- if (maxHeightString != null) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
- Integer.parseInt(maxHeightString, 16));
- }
- String categoryString = parser.getAttributeValue(null, "host_category");
- if (categoryString != null) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
- Integer.parseInt(categoryString, 16));
- }
- return options;
- }
-
- // Does this package either host or provide any active widgets?
- private boolean packageNeedsWidgetBackupLocked(String packageName) {
- int N = mAppWidgetIds.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = mAppWidgetIds.get(i);
- if (packageName.equals(id.host.packageName)) {
- // this package is hosting widgets, so it knows widget IDs
- return true;
- }
- Provider p = id.provider;
- if (p != null && packageName.equals(p.info.provider.getPackageName())) {
- // someone is hosting this app's widgets, so it knows widget IDs
- return true;
- }
- }
- return false;
- }
-
- // build the widget-state blob that we save for the app during backup.
- public byte[] getWidgetState(String backupTarget) {
- if (!mHasFeature) {
- return null;
- }
-
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- synchronized (mAppWidgetIds) {
- // Preflight: if this app neither hosts nor provides any live widgets
- // we have no work to do.
- if (!packageNeedsWidgetBackupLocked(backupTarget)) {
- return null;
- }
-
- try {
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(stream, "utf-8");
- out.startDocument(null, true);
- out.startTag(null, "ws"); // widget state
- out.attribute(null, "version", String.valueOf(WIDGET_STATE_VERSION));
- out.attribute(null, "pkg", backupTarget);
-
- // Remember all the providers that are currently hosted or published
- // by this package: that is, all of the entities related to this app
- // which will need to be told about id remapping.
- int N = mInstalledProviders.size();
- int index = 0;
- for (int i = 0; i < N; i++) {
- Provider p = mInstalledProviders.get(i);
- if (p.instances.size() > 0) {
- if (backupTarget.equals(p.info.provider.getPackageName())
- || p.isHostedBy(backupTarget)) {
- serializeProvider(out, p);
- p.tag = index++;
- }
- }
- }
-
- N = mHosts.size();
- index = 0;
- for (int i = 0; i < N; i++) {
- Host host = mHosts.get(i);
- if (backupTarget.equals(host.packageName)
- || host.hostsPackage(backupTarget)) {
- serializeHost(out, host);
- host.tag = index++;
- }
- }
-
- // All widget instances involving this package,
- // either as host or as provider
- N = mAppWidgetIds.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = mAppWidgetIds.get(i);
- if (backupTarget.equals(id.host.packageName)
- || (id.provider != null && backupTarget.equals(
- id.provider.info.provider.getPackageName()))) {
- serializeAppWidgetId(out, id);
- }
- }
-
- out.endTag(null, "ws");
- out.endDocument();
- } catch (IOException e) {
- Slog.w(TAG, "Unable to save widget state for " + backupTarget);
- return null;
- }
-
- }
- return stream.toByteArray();
- }
-
- public void restoreStarting() {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "restore starting for user " + mUserId);
- }
- synchronized (mRestoredWidgetIds) {
- // We're starting a new "system" restore operation, so any widget restore
- // state that we see from here on is intended to replace the current
- // widget configuration of any/all of the affected apps.
- mPrunedApps.clear();
- mUpdatesByProvider.clear();
- mUpdatesByHost.clear();
- }
- }
-
- // We're restoring widget state for 'pkg', so we start by wiping (a) all widget
- // instances that are hosted by that app, and (b) all instances in other hosts
- // for which 'pkg' is the provider. We assume that we'll be restoring all of
- // these hosts & providers, so will be reconstructing a correct live state.
- private void pruneWidgetStateLr(String pkg) {
- if (!mPrunedApps.contains(pkg)) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "pruning widget state for restoring package " + pkg);
- }
- for (int i = mAppWidgetIds.size() - 1; i >= 0; i--) {
- AppWidgetId id = mAppWidgetIds.get(i);
- Provider p = id.provider;
- if (id.host.packageName.equals(pkg)
- || p.info.provider.getPackageName().equals(pkg)) {
- // 'pkg' is either the host or the provider for this instances,
- // so we tear it down in anticipation of it (possibly) being
- // reconstructed due to the restore
- p.instances.remove(id);
-
- unbindAppWidgetRemoteViewsServicesLocked(id);
- mAppWidgetIds.remove(i);
- }
- }
- mPrunedApps.add(pkg);
- } else {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "already pruned " + pkg + ", continuing normally");
- }
- }
+ @Override
+ public List<String> getWidgetParticipants(int userId) {
+ return mBackupRestoreController.getWidgetParticipants(userId);
}
- // Accumulate a list of updates that affect the given provider for a final
- // coalesced notification broadcast once restore is over.
- class RestoreUpdateRecord {
- public int oldId;
- public int newId;
- public boolean notified;
-
- public RestoreUpdateRecord(int theOldId, int theNewId) {
- oldId = theOldId;
- newId = theNewId;
- notified = false;
- }
- }
-
- HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider
- = new HashMap<Provider, ArrayList<RestoreUpdateRecord>>();
- HashMap<Host, ArrayList<RestoreUpdateRecord>> mUpdatesByHost
- = new HashMap<Host, ArrayList<RestoreUpdateRecord>>();
-
- private boolean alreadyStashed(ArrayList<RestoreUpdateRecord> stash,
- final int oldId, final int newId) {
- final int N = stash.size();
- for (int i = 0; i < N; i++) {
- RestoreUpdateRecord r = stash.get(i);
- if (r.oldId == oldId && r.newId == newId) {
- return true;
- }
- }
- return false;
- }
-
- private void stashProviderRestoreUpdateLr(Provider provider, int oldId, int newId) {
- ArrayList<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider);
- if (r == null) {
- r = new ArrayList<RestoreUpdateRecord>();
- mUpdatesByProvider.put(provider, r);
- } else {
- // don't duplicate
- if (alreadyStashed(r, oldId, newId)) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "ID remap " + oldId + " -> " + newId
- + " already stashed for " + provider);
- }
- return;
- }
- }
- r.add(new RestoreUpdateRecord(oldId, newId));
- }
-
- private void stashHostRestoreUpdateLr(Host host, int oldId, int newId) {
- ArrayList<RestoreUpdateRecord> r = mUpdatesByHost.get(host);
- if (r == null) {
- r = new ArrayList<RestoreUpdateRecord>();
- mUpdatesByHost.put(host, r);
- } else {
- if (alreadyStashed(r, oldId, newId)) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "ID remap " + oldId + " -> " + newId
- + " already stashed for " + host);
- }
- return;
- }
- }
- r.add(new RestoreUpdateRecord(oldId, newId));
+ @Override
+ public byte[] getWidgetState(String packageName, int userId) {
+ return mBackupRestoreController.getWidgetState(packageName, userId);
}
- public void restoreWidgetState(String packageName, byte[] restoredState) {
- if (!mHasFeature) {
- return;
- }
-
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "Restoring widget state for " + packageName);
- }
-
- ByteArrayInputStream stream = new ByteArrayInputStream(restoredState);
- try {
- // Providers mentioned in the widget dataset by ordinal
- ArrayList<Provider> restoredProviders = new ArrayList<Provider>();
-
- // Hosts mentioned in the widget dataset by ordinal
- ArrayList<Host> restoredHosts = new ArrayList<Host>();
-
- //HashSet<String> toNotify = new HashSet<String>();
-
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(stream, null);
-
- synchronized (mAppWidgetIds) {
- synchronized (mRestoredWidgetIds) {
- int type;
- do {
- type = parser.next();
- if (type == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if ("ws".equals(tag)) {
- String v = parser.getAttributeValue(null, "version");
- String pkg = parser.getAttributeValue(null, "pkg");
-
- // TODO: fix up w.r.t. canonical vs current package names
- if (!packageName.equals(pkg)) {
- Slog.w(TAG, "Package mismatch in ws");
- return;
- }
-
- int version = Integer.parseInt(v);
- if (version > WIDGET_STATE_VERSION) {
- Slog.w(TAG, "Unable to process state version " + version);
- return;
- }
- } else if ("p".equals(tag)) {
- String pkg = parser.getAttributeValue(null, "pkg");
- String cl = parser.getAttributeValue(null, "cl");
-
- // hostedProviders index will match 'p' attribute in widget's
- // entry in the xml file being restored
- // If there's no live entry for this provider, add an inactive one
- // so that widget IDs referring to them can be properly allocated
- final ComponentName cn = new ComponentName(pkg, cl);
- Provider p = lookupProviderLocked(cn, mInstalledProviders);
- if (p == null) {
- p = new Provider();
- p.info = new AppWidgetProviderInfo();
- p.info.provider = cn;
- p.zombie = true;
- mInstalledProviders.add(p);
- }
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " provider " + cn);
- }
- restoredProviders.add(p);
- } else if ("h".equals(tag)) {
- // The host app may not yet exist on the device. If it's here we
- // just use the existing Host entry, otherwise we create a
- // placeholder whose uid will be fixed up at PACKAGE_ADDED time.
- String pkg = parser.getAttributeValue(null, "pkg");
- int uid;
- try {
- uid = getUidForPackage(pkg);
- } catch (NameNotFoundException e) {
- uid = -1;
- }
- int hostId = Integer.parseInt(
- parser.getAttributeValue(null, "id"), 16);
- Host h = lookupOrAddHostLocked(uid, pkg, hostId);
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " host[" + restoredHosts.size()
- + "]: {" + h.packageName + ":" + h.hostId + "}");
- }
- restoredHosts.add(h);
- } else if ("g".equals(tag)) {
- int restoredId = Integer.parseInt(
- parser.getAttributeValue(null, "id"), 16);
- int hostIndex = Integer.parseInt(
- parser.getAttributeValue(null, "h"), 16);
- Host host = restoredHosts.get(hostIndex);
- Provider p = null;
- String prov = parser.getAttributeValue(null, "p");
- if (prov != null) {
- // could have been null if the app had allocated an id
- // but not yet established a binding under that id
- int which = Integer.parseInt(prov, 16);
- p = restoredProviders.get(which);
- }
-
- // We'll be restoring widget state for both the host and
- // provider sides of this widget ID, so make sure we are
- // beginning from a clean slate on both fronts.
- pruneWidgetStateLr(host.packageName);
- if (p != null) {
- pruneWidgetStateLr(p.info.provider.getPackageName());
- }
-
- // Have we heard about this ancestral widget instance before?
- AppWidgetId id = findRestoredWidgetLocked(restoredId, host, p);
- if (id == null) {
- id = new AppWidgetId();
- id.appWidgetId = mNextAppWidgetId++;
- id.restoredId = restoredId;
- id.options = parseWidgetIdOptions(parser);
- id.host = host;
- id.host.instances.add(id);
- id.provider = p;
- if (id.provider != null) {
- id.provider.instances.add(id);
- }
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "New restored id " + restoredId
- + " now " + id);
- }
- mAppWidgetIds.add(id);
- }
- if (id.provider.info != null) {
- stashProviderRestoreUpdateLr(id.provider,
- restoredId, id.appWidgetId);
- } else {
- Slog.w(TAG, "Missing provider for restored widget " + id);
- }
- stashHostRestoreUpdateLr(id.host, restoredId, id.appWidgetId);
-
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " instance: " + restoredId
- + " -> " + id.appWidgetId
- + " :: p=" + id.provider);
- }
- }
- }
- } while (type != XmlPullParser.END_DOCUMENT);
-
- // We've updated our own bookkeeping. We'll need to notify the hosts and
- // providers about the changes, but we can't do that yet because the restore
- // target is not necessarily fully live at this moment. Set aside the
- // information for now; the backup manager will call us once more at the
- // end of the process when all of the targets are in a known state, and we
- // will update at that point.
- }
- }
- } catch (XmlPullParserException e) {
- Slog.w(TAG, "Unable to restore widget state for " + packageName);
- } catch (IOException e) {
- Slog.w(TAG, "Unable to restore widget state for " + packageName);
- } finally {
- saveStateAsync();
- }
- }
-
- // Called once following the conclusion of a restore operation. This is when we
- // send out updates to apps involved in widget-state restore telling them about
- // the new widget ID space.
- public void restoreFinished() {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "restoreFinished for " + mUserId);
- }
-
- final UserHandle userHandle = new UserHandle(mUserId);
- synchronized (mRestoredWidgetIds) {
- // Build the providers' broadcasts and send them off
- Set<Entry<Provider, ArrayList<RestoreUpdateRecord>>> providerEntries
- = mUpdatesByProvider.entrySet();
- for (Entry<Provider, ArrayList<RestoreUpdateRecord>> e : providerEntries) {
- // For each provider there's a list of affected IDs
- Provider provider = e.getKey();
- ArrayList<RestoreUpdateRecord> updates = e.getValue();
- final int pending = countPendingUpdates(updates);
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "Provider " + provider + " pending: " + pending);
- }
- if (pending > 0) {
- int[] oldIds = new int[pending];
- int[] newIds = new int[pending];
- final int N = updates.size();
- int nextPending = 0;
- for (int i = 0; i < N; i++) {
- RestoreUpdateRecord r = updates.get(i);
- if (!r.notified) {
- r.notified = true;
- oldIds[nextPending] = r.oldId;
- newIds[nextPending] = r.newId;
- nextPending++;
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " " + r.oldId + " => " + r.newId);
- }
- }
- }
- sendWidgetRestoreBroadcast(AppWidgetManager.ACTION_APPWIDGET_RESTORED,
- provider, null, oldIds, newIds, userHandle);
- }
- }
-
- // same thing per host
- Set<Entry<Host, ArrayList<RestoreUpdateRecord>>> hostEntries
- = mUpdatesByHost.entrySet();
- for (Entry<Host, ArrayList<RestoreUpdateRecord>> e : hostEntries) {
- Host host = e.getKey();
- if (host.uid > 0) {
- ArrayList<RestoreUpdateRecord> updates = e.getValue();
- final int pending = countPendingUpdates(updates);
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "Host " + host + " pending: " + pending);
- }
- if (pending > 0) {
- int[] oldIds = new int[pending];
- int[] newIds = new int[pending];
- final int N = updates.size();
- int nextPending = 0;
- for (int i = 0; i < N; i++) {
- RestoreUpdateRecord r = updates.get(i);
- if (!r.notified) {
- r.notified = true;
- oldIds[nextPending] = r.oldId;
- newIds[nextPending] = r.newId;
- nextPending++;
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " " + r.oldId + " => " + r.newId);
- }
- }
- }
- sendWidgetRestoreBroadcast(AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED,
- null, host, oldIds, newIds, userHandle);
- }
- }
- }
- }
+ @Override
+ public void restoreStarting(int userId) {
+ mBackupRestoreController.restoreStarting(userId);
}
- private int countPendingUpdates(ArrayList<RestoreUpdateRecord> updates) {
- int pending = 0;
- final int N = updates.size();
- for (int i = 0; i < N; i++) {
- RestoreUpdateRecord r = updates.get(i);
- if (!r.notified) {
- pending++;
- }
- }
- return pending;
+ @Override
+ public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
+ mBackupRestoreController.restoreWidgetState(packageName, restoredState, userId);
}
- void sendWidgetRestoreBroadcast(String action, Provider provider, Host host,
- int[] oldIds, int[] newIds, UserHandle userHandle) {
- Intent intent = new Intent(action);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, oldIds);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, newIds);
- if (provider != null) {
- intent.setComponent(provider.info.provider);
- mContext.sendBroadcastAsUser(intent, userHandle);
- }
- if (host != null) {
- intent.setComponent(null);
- intent.setPackage(host.packageName);
- intent.putExtra(AppWidgetManager.EXTRA_HOST_ID, host.hostId);
- mContext.sendBroadcastAsUser(intent, userHandle);
- }
+ @Override
+ public void restoreFinished(int userId) {
+ mBackupRestoreController.restoreFinished(userId);
}
- private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) {
- Provider p = null;
+ @SuppressWarnings("deprecation")
+ private Provider parseProviderInfoXml(ProviderId providerId, ResolveInfo ri) {
+ Provider provider = null;
ActivityInfo activityInfo = ri.activityInfo;
XmlResourceParser parser = null;
@@ -2165,7 +2107,7 @@ class AppWidgetServiceImpl {
AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
if (parser == null) {
Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER
- + " meta-data for " + "AppWidget provider '" + component + '\'');
+ + " meta-data for " + "AppWidget provider '" + providerId + '\'');
return null;
}
@@ -2180,19 +2122,28 @@ class AppWidgetServiceImpl {
String nodeName = parser.getName();
if (!"appwidget-provider".equals(nodeName)) {
Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for"
- + " AppWidget provider '" + component + '\'');
+ + " AppWidget provider " + providerId.componentName
+ + " for user " + providerId.uid);
return null;
}
- p = new Provider();
- AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo();
- info.provider = component;
- p.uid = activityInfo.applicationInfo.uid;
+ provider = new Provider();
+ provider.id = providerId;
+ AppWidgetProviderInfo info = provider.info = new AppWidgetProviderInfo();
+ info.provider = providerId.componentName;
+ info.providerInfo = activityInfo;
- Resources res = mContext.getPackageManager()
- .getResourcesForApplicationAsUser(activityInfo.packageName, mUserId);
+ final Resources resources;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ resources = mContext.getPackageManager()
+ .getResourcesForApplicationAsUser(activityInfo.packageName,
+ UserHandle.getUserId(providerId.uid));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
- TypedArray sa = res.obtainAttributes(attrs,
+ TypedArray sa = resources.obtainAttributes(attrs,
com.android.internal.R.styleable.AppWidgetProviderInfo);
// These dimensions has to be resolved in the application's context.
@@ -2215,10 +2166,12 @@ class AppWidgetServiceImpl {
com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0);
info.initialKeyguardLayout = sa.getResourceId(com.android.internal.R.styleable.
AppWidgetProviderInfo_initialKeyguardLayout, 0);
+
String className = sa
.getString(com.android.internal.R.styleable.AppWidgetProviderInfo_configure);
if (className != null) {
- info.configure = new ComponentName(component.getPackageName(), className);
+ info.configure = new ComponentName(providerId.componentName.getPackageName(),
+ className);
}
info.label = activityInfo.loadLabel(mContext.getPackageManager()).toString();
info.icon = ri.getIconResource();
@@ -2234,111 +2187,224 @@ class AppWidgetServiceImpl {
AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
sa.recycle();
- } catch (Exception e) {
+ } catch (IOException | PackageManager.NameNotFoundException | XmlPullParserException e) {
// Ok to catch Exception here, because anything going wrong because
// of what a client process passes to us should not be fatal for the
// system process.
- Slog.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e);
+ Slog.w(TAG, "XML parsing failed for AppWidget provider "
+ + providerId.componentName + " for user " + providerId.uid, e);
return null;
} finally {
- if (parser != null)
+ if (parser != null) {
parser.close();
+ }
}
- return p;
+ return provider;
}
- int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException {
+ private int getUidForPackage(String packageName, int userId) {
PackageInfo pkgInfo = null;
+
+ final long identity = Binder.clearCallingIdentity();
try {
- pkgInfo = mPm.getPackageInfo(packageName, 0, mUserId);
+ pkgInfo = mPackageManager.getPackageInfo(packageName, 0, userId);
} catch (RemoteException re) {
// Shouldn't happen, local call
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
+
if (pkgInfo == null || pkgInfo.applicationInfo == null) {
- throw new PackageManager.NameNotFoundException();
+ return -1;
}
+
return pkgInfo.applicationInfo.uid;
}
- int enforceSystemOrCallingUid(String packageName) throws IllegalArgumentException {
- int callingUid = Binder.getCallingUid();
- if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID || callingUid == 0) {
- return callingUid;
+ private ActivityInfo getProviderInfo(ComponentName componentName, int userId) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.setComponent(componentName);
+
+ List<ResolveInfo> receivers = queryIntentReceivers(intent, userId);
+ // We are setting component, so there is only one or none.
+ if (!receivers.isEmpty()) {
+ return receivers.get(0).activityInfo;
}
- return enforceCallingUid(packageName);
+
+ return null;
}
- int enforceCallingUid(String packageName) throws IllegalArgumentException {
- int callingUid = Binder.getCallingUid();
- int packageUid;
+ private List<ResolveInfo> queryIntentReceivers(Intent intent, int userId) {
+ final long identity = Binder.clearCallingIdentity();
try {
- packageUid = getUidForPackage(packageName);
- } catch (PackageManager.NameNotFoundException ex) {
- throw new IllegalArgumentException("packageName and uid don't match packageName="
- + packageName);
- }
- if (!UserHandle.isSameApp(callingUid, packageUid)) {
- throw new IllegalArgumentException("packageName and uid don't match packageName="
- + packageName);
+ return mPackageManager.queryIntentReceivers(intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.GET_META_DATA, userId);
+ } catch (RemoteException re) {
+ return Collections.emptyList();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- return callingUid;
}
- void sendInitialBroadcasts() {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- final int N = mInstalledProviders.size();
+ private void onUserStarted(int userId) {
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ final int N = mProviders.size();
for (int i = 0; i < N; i++) {
- Provider p = mInstalledProviders.get(i);
- if (p.instances.size() > 0) {
- sendEnableIntentLocked(p);
- int[] appWidgetIds = getAppWidgetIds(p);
- sendUpdateIntentLocked(p, appWidgetIds);
- registerForBroadcastsLocked(p, appWidgetIds);
+ Provider provider = mProviders.get(i);
+
+ // Send broadcast only to the providers of the user.
+ if (provider.getUserId() != userId) {
+ continue;
+ }
+
+ if (provider.widgets.size() > 0) {
+ sendEnableIntentLocked(provider);
+ int[] appWidgetIds = getWidgetIds(provider.widgets);
+ sendUpdateIntentLocked(provider, appWidgetIds);
+ registerForBroadcastsLocked(provider, appWidgetIds);
}
}
}
}
// only call from initialization -- it assumes that the data structures are all empty
- void loadStateLocked() {
- AtomicFile file = savedStateFile();
- try {
- FileInputStream stream = file.openRead();
- readStateFromFileLocked(stream);
+ private void loadGroupStateLocked(int[] profileIds) {
+ // We can bind the widgets to host and providers only after
+ // reading the host and providers for all users since a widget
+ // can have a host and a provider in different users.
+ List<LoadedWidgetState> loadedWidgets = new ArrayList<>();
- if (stream != null) {
- try {
- stream.close();
- } catch (IOException e) {
- Slog.w(TAG, "Failed to close state FileInputStream " + e);
- }
+ int version = 0;
+
+ final int profileIdCount = profileIds.length;
+ for (int i = 0; i < profileIdCount; i++) {
+ final int profileId = profileIds[i];
+
+ // No file written for this user - nothing to do.
+ AtomicFile file = getSavedStateFile(profileId);
+ try {
+ FileInputStream stream = file.openRead();
+ version = readProfileStateFromFileLocked(stream, profileId, loadedWidgets);
+ IoUtils.closeQuietly(stream);
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "Failed to read state: " + e);
+ }
+ }
+
+ if (version >= 0) {
+ // Hooke'm up...
+ bindLoadedWidgets(loadedWidgets);
+
+ // upgrade the database if needed
+ performUpgradeLocked(version);
+ } else {
+ // failed reading, clean up
+ Slog.w(TAG, "Failed to read state, clearing widgets and hosts.");
+ mWidgets.clear();
+ mHosts.clear();
+ final int N = mProviders.size();
+ for (int i = 0; i < N; i++) {
+ mProviders.get(i).widgets.clear();
}
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "Failed to read state: " + e);
}
}
- void saveStateLocked() {
- if (!mHasFeature) {
- return;
+ private void bindLoadedWidgets(List<LoadedWidgetState> loadedWidgets) {
+ final int loadedWidgetCount = loadedWidgets.size();
+ for (int i = loadedWidgetCount - 1; i >= 0; i--) {
+ LoadedWidgetState loadedWidget = loadedWidgets.remove(i);
+ Widget widget = loadedWidget.widget;
+
+ widget.provider = findProviderByTag(loadedWidget.providerTag);
+ if (widget.provider == null) {
+ // This provider is gone. We just let the host figure out
+ // that this happened when it fails to load it.
+ continue;
+ }
+
+ widget.host = findHostByTag(loadedWidget.hostTag);
+ if (widget.host == null) {
+ // This host is gone.
+ continue;
+ }
+
+ widget.provider.widgets.add(widget);
+ widget.host.widgets.add(widget);
+ mWidgets.add(widget);
}
- AtomicFile file = savedStateFile();
- FileOutputStream stream;
- try {
- stream = file.startWrite();
- if (writeStateToFileLocked(stream)) {
- file.finishWrite(stream);
- } else {
- file.failWrite(stream);
- Slog.w(TAG, "Failed to save state, restoring backup.");
+ }
+
+ private Provider findProviderByTag(int tag) {
+ if (tag < 0) {
+ return null;
+ }
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ if (provider.tag == tag) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ private Host findHostByTag(int tag) {
+ if (tag < 0) {
+ return null;
+ }
+ final int hostCount = mHosts.size();
+ for (int i = 0; i < hostCount; i++) {
+ Host host = mHosts.get(i);
+ if (host.tag == tag) {
+ return host;
+ }
+ }
+ return null;
+ }
+
+ private void saveStateLocked(int userId) {
+ tagProvidersAndHosts();
+
+ final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
+
+ final int profileCount = profileIds.length;
+ for (int i = 0; i < profileCount; i++) {
+ final int profileId = profileIds[i];
+
+ AtomicFile file = getSavedStateFile(profileId);
+ FileOutputStream stream;
+ try {
+ stream = file.startWrite();
+ if (writeProfileStateToFileLocked(stream, profileId)) {
+ file.finishWrite(stream);
+ } else {
+ file.failWrite(stream);
+ Slog.w(TAG, "Failed to save state, restoring backup.");
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed open state file for write: " + e);
}
- } catch (IOException e) {
- Slog.w(TAG, "Failed open state file for write: " + e);
}
}
- boolean writeStateToFileLocked(FileOutputStream stream) {
+ private void tagProvidersAndHosts() {
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ provider.tag = i;
+ }
+
+ final int hostCount = mHosts.size();
+ for (int i = 0; i < hostCount; i++) {
+ Host host = mHosts.get(i);
+ host.tag = i;
+ }
+ }
+
+ private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
int N;
try {
@@ -2347,39 +2413,52 @@ class AppWidgetServiceImpl {
out.startDocument(null, true);
out.startTag(null, "gs");
out.attribute(null, "version", String.valueOf(CURRENT_VERSION));
- int providerIndex = 0;
- N = mInstalledProviders.size();
+
+ N = mProviders.size();
for (int i = 0; i < N; i++) {
- Provider p = mInstalledProviders.get(i);
- if (p.instances.size() > 0) {
- serializeProvider(out, p);
- p.tag = providerIndex;
- providerIndex++;
+ Provider provider = mProviders.get(i);
+ // Save only providers for the user.
+ if (provider.getUserId() != userId) {
+ continue;
+ }
+ if (provider.widgets.size() > 0) {
+ serializeProvider(out, provider);
}
}
N = mHosts.size();
for (int i = 0; i < N; i++) {
Host host = mHosts.get(i);
+ // Save only hosts for the user.
+ if (host.getUserId() != userId) {
+ continue;
+ }
serializeHost(out, host);
- host.tag = i;
}
- N = mAppWidgetIds.size();
+ N = mWidgets.size();
for (int i = 0; i < N; i++) {
- AppWidgetId id = mAppWidgetIds.get(i);
- serializeAppWidgetId(out, id);
+ Widget widget = mWidgets.get(i);
+ // Save only widgets hosted by the user.
+ if (widget.host.getUserId() != userId) {
+ continue;
+ }
+ serializeAppWidget(out, widget);
}
- Iterator<String> it = mPackagesWithBindWidgetPermission.iterator();
+ Iterator<Pair<Integer, String>> it = mPackagesWithBindWidgetPermission.iterator();
while (it.hasNext()) {
+ Pair<Integer, String> binding = it.next();
+ // Save only white listings for the user.
+ if (binding.first != userId) {
+ continue;
+ }
out.startTag(null, "b");
- out.attribute(null, "packageName", it.next());
+ out.attribute(null, "packageName", binding.second);
out.endTag(null, "b");
}
out.endTag(null, "gs");
-
out.endDocument();
return true;
} catch (IOException e) {
@@ -2388,17 +2467,16 @@ class AppWidgetServiceImpl {
}
}
- @SuppressWarnings("unused")
- void readStateFromFileLocked(FileInputStream stream) {
- boolean success = false;
- int version = 0;
+ private int readProfileStateFromFileLocked(FileInputStream stream, int userId,
+ List<LoadedWidgetState> outLoadedWidgets) {
+ int version = -1;
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, null);
+ int legacyProviderIndex = -1;
+ int legacyHostIndex = -1;
int type;
- int providerIndex = 0;
- HashMap<Integer, Provider> loadedProviders = new HashMap<Integer, Provider>();
do {
type = parser.next();
if (type == XmlPullParser.START_TAG) {
@@ -2411,67 +2489,89 @@ class AppWidgetServiceImpl {
version = 0;
}
} else if ("p".equals(tag)) {
+ legacyProviderIndex++;
// TODO: do we need to check that this package has the same signature
// as before?
String pkg = parser.getAttributeValue(null, "pkg");
String cl = parser.getAttributeValue(null, "cl");
- final IPackageManager packageManager = AppGlobals.getPackageManager();
- try {
- packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0, mUserId);
- } catch (RemoteException e) {
- String[] pkgs = mContext.getPackageManager()
- .currentToCanonicalPackageNames(new String[] { pkg });
- pkg = pkgs[0];
+ pkg = getCanonicalPackageName(pkg, cl, userId);
+ if (pkg == null) {
+ continue;
}
- Provider p = lookupProviderLocked(new ComponentName(pkg, cl));
- if (p == null && mSafeMode) {
- // if we're in safe mode, make a temporary one
- p = new Provider();
- p.info = new AppWidgetProviderInfo();
- p.info.provider = new ComponentName(pkg, cl);
- p.zombie = true;
- mInstalledProviders.add(p);
+ final int uid = getUidForPackage(pkg, userId);
+ if (uid < 0) {
+ continue;
+ }
+
+ ComponentName componentName = new ComponentName(pkg, cl);
+
+ ActivityInfo providerInfo = getProviderInfo(componentName, userId);
+ if (providerInfo == null) {
+ continue;
}
- if (p != null) {
- // if it wasn't uninstalled or something
- loadedProviders.put(providerIndex, p);
+
+ ProviderId providerId = new ProviderId(uid, componentName);
+ Provider provider = lookupProviderLocked(providerId);
+
+ if (provider == null && mSafeMode) {
+ // if we're in safe mode, make a temporary one
+ provider = new Provider();
+ provider.info = new AppWidgetProviderInfo();
+ provider.info.provider = providerId.componentName;
+ provider.info.providerInfo = providerInfo;
+ provider.zombie = true;
+ provider.id = providerId;
+ mProviders.add(provider);
}
- providerIndex++;
+
+ String tagAttribute = parser.getAttributeValue(null, "tag");
+ final int providerTag = !TextUtils.isEmpty(tagAttribute)
+ ? Integer.parseInt(tagAttribute, 16) : legacyProviderIndex;
+ provider.tag = providerTag;
} else if ("h".equals(tag)) {
+ legacyHostIndex++;
Host host = new Host();
-
// TODO: do we need to check that this package has the same signature
// as before?
- host.packageName = parser.getAttributeValue(null, "pkg");
- try {
- host.uid = getUidForPackage(host.packageName);
- } catch (PackageManager.NameNotFoundException ex) {
+ String pkg = parser.getAttributeValue(null, "pkg");
+
+ final int uid = getUidForPackage(pkg, userId);
+ if (uid < 0) {
host.zombie = true;
}
+
if (!host.zombie || mSafeMode) {
// In safe mode, we don't discard the hosts we don't recognize
// so that they're not pruned from our list. Otherwise, we do.
- host.hostId = Integer
- .parseInt(parser.getAttributeValue(null, "id"), 16);
+ final int hostId = Integer.parseInt(parser.getAttributeValue(
+ null, "id"), 16);
+
+ String tagAttribute = parser.getAttributeValue(null, "tag");
+ final int hostTag = !TextUtils.isEmpty(tagAttribute)
+ ? Integer.parseInt(tagAttribute, 16) : legacyHostIndex;
+
+ host.tag = hostTag;
+ host.id = new HostId(uid, hostId, pkg);
mHosts.add(host);
}
} else if ("b".equals(tag)) {
String packageName = parser.getAttributeValue(null, "packageName");
- if (packageName != null) {
- mPackagesWithBindWidgetPermission.add(packageName);
+ final int uid = getUidForPackage(packageName, userId);
+ if (uid >= 0) {
+ Pair<Integer, String> packageId = Pair.create(userId, packageName);
+ mPackagesWithBindWidgetPermission.add(packageId);
}
} else if ("g".equals(tag)) {
- AppWidgetId id = new AppWidgetId();
- id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16);
- if (id.appWidgetId >= mNextAppWidgetId) {
- mNextAppWidgetId = id.appWidgetId + 1;
- }
+ Widget widget = new Widget();
+ widget.appWidgetId = Integer.parseInt(parser.getAttributeValue(
+ null, "id"), 16);
+ setMinAppWidgetIdLocked(userId, widget.appWidgetId + 1);
// restored ID is allowed to be absent
String restoredIdString = parser.getAttributeValue(null, "rid");
- id.restoredId = (restoredIdString == null) ? 0
+ widget.restoredId = (restoredIdString == null) ? 0
: Integer.parseInt(restoredIdString, 16);
Bundle options = new Bundle();
@@ -2500,92 +2600,57 @@ class AppWidgetServiceImpl {
options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
Integer.parseInt(categoryString, 16));
}
- id.options = options;
+ widget.options = options;
+ final int hostTag = Integer.parseInt(parser.getAttributeValue(
+ null, "h"), 16);
String providerString = parser.getAttributeValue(null, "p");
- if (providerString != null) {
- // there's no provider if it hasn't been bound yet.
- // maybe we don't have to save this, but it brings the system
- // to the state it was in.
- int pIndex = Integer.parseInt(providerString, 16);
- id.provider = loadedProviders.get(pIndex);
- if (false) {
- Slog.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider "
- + pIndex + " which is " + id.provider);
- }
- if (id.provider == null) {
- // This provider is gone. We just let the host figure out
- // that this happened when it fails to load it.
- continue;
- }
- }
-
- int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16);
- id.host = mHosts.get(hIndex);
- if (id.host == null) {
- // This host is gone.
- continue;
- }
-
- if (id.provider != null) {
- id.provider.instances.add(id);
- }
- id.host.instances.add(id);
- mAppWidgetIds.add(id);
+ final int providerTag = (providerString != null) ? Integer.parseInt(
+ parser.getAttributeValue(null, "p"), 16) : TAG_UNDEFINED;
+
+ // We can match widgets with hosts and providers only after hosts
+ // and providers for all users have been loaded since the widget
+ // host and provider can be in different user profiles.
+ LoadedWidgetState loadedWidgets = new LoadedWidgetState(widget,
+ hostTag, providerTag);
+ outLoadedWidgets.add(loadedWidgets);
}
}
} while (type != XmlPullParser.END_DOCUMENT);
- success = true;
- } catch (NullPointerException e) {
- Slog.w(TAG, "failed parsing " + e);
- } catch (NumberFormatException e) {
- Slog.w(TAG, "failed parsing " + e);
- } catch (XmlPullParserException e) {
- Slog.w(TAG, "failed parsing " + e);
- } catch (IOException e) {
- Slog.w(TAG, "failed parsing " + e);
- } catch (IndexOutOfBoundsException e) {
+ } catch (NullPointerException
+ | NumberFormatException
+ | XmlPullParserException
+ | IOException
+ | IndexOutOfBoundsException e) {
Slog.w(TAG, "failed parsing " + e);
+ return -1;
}
- if (success) {
- // delete any hosts that didn't manage to get connected (should happen)
- // if it matters, they'll be reconnected.
- for (int i = mHosts.size() - 1; i >= 0; i--) {
- pruneHostLocked(mHosts.get(i));
- }
- // upgrade the database if needed
- performUpgrade(version);
- } else {
- // failed reading, clean up
- Slog.w(TAG, "Failed to read state, clearing widgets and hosts.");
-
- mAppWidgetIds.clear();
- mHosts.clear();
- final int N = mInstalledProviders.size();
- for (int i = 0; i < N; i++) {
- mInstalledProviders.get(i).instances.clear();
- }
- }
+ return version;
}
- private void performUpgrade(int fromVersion) {
+ private void performUpgradeLocked(int fromVersion) {
if (fromVersion < CURRENT_VERSION) {
- Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to " + CURRENT_VERSION
- + " for user " + mUserId);
+ Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to "
+ + CURRENT_VERSION);
}
int version = fromVersion;
// Update 1: keyguard moved from package "android" to "com.android.keyguard"
if (version == 0) {
- for (int i = 0; i < mHosts.size(); i++) {
- Host host = mHosts.get(i);
- if (host != null && "android".equals(host.packageName)
- && host.hostId == KEYGUARD_HOST_ID) {
- host.packageName = KEYGUARD_HOST_PACKAGE;
+ HostId oldHostId = new HostId(Process.myUid(),
+ KEYGUARD_HOST_ID, OLD_KEYGUARD_HOST_PACKAGE);
+
+ Host host = lookupHostLocked(oldHostId);
+ if (host != null) {
+ final int uid = getUidForPackage(NEW_KEYGUARD_HOST_PACKAGE,
+ UserHandle.USER_OWNER);
+ if (uid >= 0) {
+ host.id = new HostId(uid, KEYGUARD_HOST_ID, NEW_KEYGUARD_HOST_PACKAGE);
}
}
+
version = 1;
}
@@ -2594,19 +2659,19 @@ class AppWidgetServiceImpl {
}
}
- static File getSettingsFile(int userId) {
- return new File(Environment.getUserSystemDirectory(userId), SETTINGS_FILENAME);
+ private static File getStateFile(int userId) {
+ return new File(Environment.getUserSystemDirectory(userId), STATE_FILENAME);
}
- AtomicFile savedStateFile() {
- File dir = Environment.getUserSystemDirectory(mUserId);
- File settingsFile = getSettingsFile(mUserId);
- if (!settingsFile.exists() && mUserId == 0) {
+ private static AtomicFile getSavedStateFile(int userId) {
+ File dir = Environment.getUserSystemDirectory(userId);
+ File settingsFile = getStateFile(userId);
+ if (!settingsFile.exists() && userId == UserHandle.USER_OWNER) {
if (!dir.exists()) {
dir.mkdirs();
}
// Migrate old data
- File oldFile = new File("/data/system/" + SETTINGS_FILENAME);
+ File oldFile = new File("/data/system/" + STATE_FILENAME);
// Method doesn't throw an exception on failure. Ignore any errors
// in moving the file (like non-existence)
oldFile.renameTo(settingsFile);
@@ -2614,45 +2679,67 @@ class AppWidgetServiceImpl {
return new AtomicFile(settingsFile);
}
- void onUserStopping() {
- // prune the ones we don't want to keep
- int N = mInstalledProviders.size();
- for (int i = N - 1; i >= 0; i--) {
- Provider p = mInstalledProviders.get(i);
- cancelBroadcasts(p);
- }
- }
+ private void onUserStopped(int userId) {
+ synchronized (mLock) {
+ // Remove widgets that have both host and provider in the user.
+ final int widgetCount = mWidgets.size();
+ for (int i = widgetCount - 1; i >= 0; i--) {
+ Widget widget = mWidgets.get(i);
+
+ final boolean hostInUser = widget.host.getUserId() == userId;
+ final boolean hasProvider = widget.provider != null;
+ final boolean providerInUser = hasProvider && widget.provider.getUserId() == userId;
+
+ // If both host and provider are in the user, just drop the widgets
+ // as we do not want to make host callbacks and provider broadcasts
+ // as the host and the provider will be killed.
+ if (hostInUser && (!hasProvider || providerInUser)) {
+ mWidgets.remove(i);
+ widget.host.widgets.remove(widget);
+ widget.host = null;
+ if (hasProvider) {
+ widget.provider.widgets.remove(widget);
+ widget.provider = null;
+ }
+ }
+ }
- void onUserRemoved() {
- getSettingsFile(mUserId).delete();
- }
+ // Remove hosts and notify providers in other profiles.
+ final int hostCount = mHosts.size();
+ for (int i = hostCount - 1; i >= 0; i--) {
+ Host host = mHosts.get(i);
+ if (host.getUserId() == userId) {
+ deleteHostLocked(host);
+ }
+ }
- boolean addProvidersForPackageLocked(String pkgName) {
- boolean providersAdded = false;
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- intent.setPackage(pkgName);
- List<ResolveInfo> broadcastReceivers;
- try {
- broadcastReceivers = mPm.queryIntentReceivers(intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.GET_META_DATA, mUserId);
- } catch (RemoteException re) {
- // Shouldn't happen, local call
- return false;
- }
- final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
- for (int i = 0; i < N; i++) {
- ResolveInfo ri = broadcastReceivers.get(i);
- ActivityInfo ai = ri.activityInfo;
- if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
- continue;
+ // Remove the providers and notify hosts in other profiles.
+ final int providerCount = mProviders.size();
+ for (int i = providerCount - 1; i >= 0; i--) {
+ Provider provider = mProviders.get(i);
+ if (provider.getUserId() == userId) {
+ deleteProviderLocked(provider);
+ }
}
- if (pkgName.equals(ai.packageName)) {
- providersAdded = addProviderLocked(ri);
+
+ // Remove grants for this user.
+ final int grantCount = mPackagesWithBindWidgetPermission.size();
+ for (int i = grantCount - 1; i >= 0; i--) {
+ Pair<Integer, String> packageId = mPackagesWithBindWidgetPermission.valueAt(i);
+ if (packageId.first == userId) {
+ mPackagesWithBindWidgetPermission.removeAt(i);
+ }
+ }
+
+ // Take a note we no longer have state for this user.
+ final int index = mLoadedUserIds.indexOfKey(userId);
+ if (index >= 0) {
+ mLoadedUserIds.removeAt(index);
}
- }
- return providersAdded;
+ // Remove the widget id counter.
+ mNextAppWidgetIds.removeAt(userId);
+ }
}
/**
@@ -2661,71 +2748,59 @@ class AppWidgetServiceImpl {
*
* @return whether any providers were updated
*/
- boolean updateProvidersForPackageLocked(String pkgName, Set<ComponentName> removedProviders) {
+ private boolean updateProvidersForPackageLocked(String packageName, int userId,
+ Set<ProviderId> removedProviders) {
boolean providersUpdated = false;
- HashSet<String> keep = new HashSet<String>();
+
+ HashSet<ProviderId> keep = new HashSet<>();
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- intent.setPackage(pkgName);
- List<ResolveInfo> broadcastReceivers;
- try {
- broadcastReceivers = mPm.queryIntentReceivers(intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.GET_META_DATA, mUserId);
- } catch (RemoteException re) {
- // Shouldn't happen, local call
- return false;
- }
+ intent.setPackage(packageName);
+ List<ResolveInfo> broadcastReceivers = queryIntentReceivers(intent, userId);
// add the missing ones and collect which ones to keep
int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
for (int i = 0; i < N; i++) {
ResolveInfo ri = broadcastReceivers.get(i);
ActivityInfo ai = ri.activityInfo;
+
if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
continue;
}
- if (pkgName.equals(ai.packageName)) {
- ComponentName component = new ComponentName(ai.packageName, ai.name);
- Provider p = lookupProviderLocked(component);
- if (p == null) {
+
+ if (packageName.equals(ai.packageName)) {
+ ProviderId providerId = new ProviderId(ai.applicationInfo.uid,
+ new ComponentName(ai.packageName, ai.name));
+
+ Provider provider = lookupProviderLocked(providerId);
+ if (provider == null) {
if (addProviderLocked(ri)) {
- keep.add(ai.name);
+ keep.add(providerId);
providersUpdated = true;
}
} else {
- Provider parsed = parseProviderInfoXml(component, ri);
+ Provider parsed = parseProviderInfoXml(providerId, ri);
if (parsed != null) {
- keep.add(ai.name);
+ keep.add(providerId);
// Use the new AppWidgetProviderInfo.
- p.info = parsed.info;
+ provider.info = parsed.info;
// If it's enabled
- final int M = p.instances.size();
+ final int M = provider.widgets.size();
if (M > 0) {
- int[] appWidgetIds = getAppWidgetIds(p);
+ int[] appWidgetIds = getWidgetIds(provider.widgets);
// Reschedule for the new updatePeriodMillis (don't worry about handling
// it specially if updatePeriodMillis didn't change because we just sent
// an update, and the next one will be updatePeriodMillis from now).
- cancelBroadcasts(p);
- registerForBroadcastsLocked(p, appWidgetIds);
+ cancelBroadcasts(provider);
+ registerForBroadcastsLocked(provider, appWidgetIds);
// If it's currently showing, call back with the new
// AppWidgetProviderInfo.
for (int j = 0; j < M; j++) {
- AppWidgetId id = p.instances.get(j);
- id.views = null;
- if (id.host != null && id.host.callbacks != null) {
- try {
- id.host.callbacks.providerChanged(id.appWidgetId, p.info,
- mUserId);
- } catch (RemoteException ex) {
- // 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;
- }
- }
+ Widget widget = provider.widgets.get(j);
+ widget.views = null;
+ scheduleNotifyProviderChangedLocked(widget);
}
// Now that we've told the host, push out an update.
- sendUpdateIntentLocked(p, appWidgetIds);
+ sendUpdateIntentLocked(provider, appWidgetIds);
providersUpdated = true;
}
}
@@ -2734,15 +2809,16 @@ class AppWidgetServiceImpl {
}
// prune the ones we don't want to keep
- N = mInstalledProviders.size();
+ N = mProviders.size();
for (int i = N - 1; i >= 0; i--) {
- Provider p = mInstalledProviders.get(i);
- if (pkgName.equals(p.info.provider.getPackageName())
- && !keep.contains(p.info.provider.getClassName())) {
+ Provider provider = mProviders.get(i);
+ if (packageName.equals(provider.info.provider.getPackageName())
+ && provider.getUserId() == userId
+ && !keep.contains(provider.id)) {
if (removedProviders != null) {
- removedProviders.add(p.info.provider);
+ removedProviders.add(provider.id);
}
- removeProviderLocked(i, p);
+ deleteProviderLocked(provider);
providersUpdated = true;
}
}
@@ -2750,46 +2826,1249 @@ class AppWidgetServiceImpl {
return providersUpdated;
}
- boolean removeProvidersForPackageLocked(String pkgName) {
- boolean providersRemoved = false;
- int N = mInstalledProviders.size();
+ private boolean removeHostsAndProvidersForPackageLocked(String pkgName, int userId) {
+ boolean removed = false;
+
+ int N = mProviders.size();
for (int i = N - 1; i >= 0; i--) {
- Provider p = mInstalledProviders.get(i);
- if (pkgName.equals(p.info.provider.getPackageName())) {
- removeProviderLocked(i, p);
- providersRemoved = true;
+ Provider provider = mProviders.get(i);
+ if (pkgName.equals(provider.info.provider.getPackageName())
+ && provider.getUserId() == userId) {
+ deleteProviderLocked(provider);
+ removed = true;
}
}
// Delete the hosts for this package too
- //
// By now, we have removed any AppWidgets that were in any hosts here,
// so we don't need to worry about sending DISABLE broadcasts to them.
N = mHosts.size();
for (int i = N - 1; i >= 0; i--) {
Host host = mHosts.get(i);
- if (pkgName.equals(host.packageName)) {
+ if (pkgName.equals(host.id.packageName)
+ && host.getUserId() == userId) {
deleteHostLocked(host);
+ removed = true;
}
}
- return providersRemoved;
+ return removed;
}
- void notifyHostsForProvidersChangedLocked() {
- final int N = mHosts.size();
- for (int i = N - 1; i >= 0; i--) {
- Host host = mHosts.get(i);
+ private String getCanonicalPackageName(String packageName, String className, int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
try {
- if (host.callbacks != null) {
- host.callbacks.providersChanged(mUserId);
+ AppGlobals.getPackageManager().getReceiverInfo(new ComponentName(packageName,
+ className), 0, userId);
+ return packageName;
+ } catch (RemoteException re) {
+ String[] packageNames = mContext.getPackageManager()
+ .currentToCanonicalPackageNames(new String[]{packageName});
+ if (packageNames != null && packageNames.length > 0) {
+ return packageNames[0];
}
- } catch (RemoteException ex) {
- // It failed; remove the callback. No need to prune because
- // we know that this host is still referenced by this
- // instance.
- host.callbacks = null;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return null;
+ }
+
+ private void sendBroadcastAsUser(Intent intent, UserHandle userHandle) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mContext.sendBroadcastAsUser(intent, userHandle);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void bindService(Intent intent, ServiceConnection connection,
+ UserHandle userHandle) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+ userHandle);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void unbindService(ServiceConnection connection) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.unbindService(connection);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private final class CallbackHandler extends Handler {
+ public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
+ public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
+ public static final int MSG_NOTIFY_PROVIDERS_CHANGED = 3;
+ public static final int MSG_NOTIFY_VIEW_DATA_CHANGED = 4;
+
+ public CallbackHandler(Looper looper) {
+ super(looper, null, false);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_NOTIFY_UPDATE_APP_WIDGET: {
+ SomeArgs args = (SomeArgs) message.obj;
+ Host host = (Host) args.arg1;
+ IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+ RemoteViews views = (RemoteViews) args.arg3;
+ final int appWidgetId = args.argi1;
+ args.recycle();
+
+ handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views);
+ } break;
+
+ case MSG_NOTIFY_PROVIDER_CHANGED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ Host host = (Host) args.arg1;
+ IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+ AppWidgetProviderInfo info = (AppWidgetProviderInfo)args.arg3;
+ final int appWidgetId = args.argi1;
+ args.recycle();
+
+ handleNotifyProviderChanged(host, callbacks, appWidgetId, info);
+ } break;
+
+ case MSG_NOTIFY_PROVIDERS_CHANGED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ Host host = (Host) args.arg1;
+ IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+ args.recycle();
+
+ handleNotifyProvidersChanged(host, callbacks);
+ } break;
+
+ case MSG_NOTIFY_VIEW_DATA_CHANGED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ Host host = (Host) args.arg1;
+ IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+ final int appWidgetId = args.argi1;
+ final int viewId = args.argi2;
+ args.recycle();
+
+ handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId);
+ } break;
+ }
+ }
+ }
+
+ private final class SecurityPolicy {
+
+ public int[] resolveCallerEnabledGroupProfiles(int[] profileIds) {
+ final int parentId = UserHandle.getCallingUserId();
+
+ int enabledProfileCount = 0;
+ final int profileCount = profileIds.length;
+ for (int i = 0; i < profileCount; i++) {
+ final int profileId = profileIds[i];
+ if (!isParentOrProfile(parentId, profileId)) {
+ throw new SecurityException("Not the current user or"
+ + " a child profile: " + profileId);
+ }
+ if (!isProfileEnabled(profileId)) {
+ profileIds[i] = DISABLED_PROFILE;
+ } else {
+ enabledProfileCount++;
+ }
+ }
+
+ int resolvedProfileIndex = 0;
+ final int[] resolvedProfiles = new int[enabledProfileCount];
+ for (int i = 0; i < profileCount; i++) {
+ final int profileId = profileIds[i];
+ if (profileId != DISABLED_PROFILE) {
+ resolvedProfiles[resolvedProfileIndex] = profileId;
+ resolvedProfileIndex++;
+ }
+ }
+
+ return resolvedProfiles;
+ }
+
+ public int[] getEnabledGroupProfileIds(int userId) {
+ final int parentId = getGroupParent(userId);
+
+ final List<UserInfo> profiles;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ profiles = mUserManager.getProfiles(parentId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ int enabledProfileCount = 0;
+ final int profileCount = profiles.size();
+ for (int i = 0; i < profileCount; i++) {
+ if (profiles.get(i).isEnabled()) {
+ enabledProfileCount++;
+ }
+ }
+
+ int enabledProfileIndex = 0;
+ final int[] profileIds = new int[enabledProfileCount];
+ for (int i = 0; i < profileCount; i++) {
+ UserInfo profile = profiles.get(i);
+ if (profile.isEnabled()) {
+ profileIds[enabledProfileIndex] = profile.getUserHandle().getIdentifier();
+ enabledProfileIndex++;
+ }
+ }
+
+ return profileIds;
+ }
+
+ public void enforceServiceExistsAndRequiresBindRemoteViewsPermission(
+ ComponentName componentName, int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ ServiceInfo serviceInfo = mPackageManager.getServiceInfo(componentName,
+ PackageManager.GET_PERMISSIONS, userId);
+ if (serviceInfo == null) {
+ throw new SecurityException("Service " + componentName
+ + " not installed for user " + userId);
+ }
+ if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(serviceInfo.permission)) {
+ throw new SecurityException("Service " + componentName
+ + " in user " + userId + "does not require "
+ + android.Manifest.permission.BIND_REMOTEVIEWS);
+ }
+ } catch (RemoteException re) {
+ // Local call - shouldn't happen.
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void enforceModifyAppWidgetBindPermissions(String packageName) {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
+ "hasBindAppWidgetPermission packageName=" + packageName);
+ }
+
+ public void enforceCallFromPackage(String packageName) {
+ if (!isCallFromPackage(packageName)) {
+ throw new SecurityException("Package " + packageName
+ + " not running under user " + UserHandle.getCallingUserId());
+ }
+ }
+
+ public boolean isCallFromPackage(String packageName) {
+ // System and root call all from anywhere they want.
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.SYSTEM_UID || callingUid == 0 /* root */) {
+ return true;
+ }
+ // Check if the package is present for the given profile.
+ final int packageUid = getUidForPackage(packageName,
+ UserHandle.getUserId(callingUid));
+ if (packageUid < 0) {
+ return false;
+ }
+ // Check if the call for a package is coming from that package.
+ return UserHandle.isSameApp(callingUid, packageUid);
+ }
+
+ public boolean hasCallerBindPermissionOrBindWhiteListedLocked(String packageName) {
+ try {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BIND_APPWIDGET, null);
+ } catch (SecurityException se) {
+ if (!isCallerBindAppWidgetWhiteListedLocked(packageName)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isCallerBindAppWidgetWhiteListedLocked(String packageName) {
+ final int userId = UserHandle.getCallingUserId();
+ final int packageUid = getUidForPackage(packageName, userId);
+ if (packageUid < 0) {
+ throw new IllegalArgumentException("No package " + packageName
+ + " for user " + userId);
+ }
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ Pair<Integer, String> packageId = Pair.create(userId, packageName);
+ if (mPackagesWithBindWidgetPermission.contains(packageId)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean canAccessAppWidget(Widget widget, int uid, String packageName) {
+ if (isHostInPackageForUid(widget.host, uid, packageName)) {
+ // Apps hosting the AppWidget have access to it.
+ return true;
+ }
+ if (isProviderInPackageForUid(widget.provider, uid, packageName)) {
+ // Apps providing the AppWidget have access to it.
+ return true;
+ }
+ if (isHostAccessingProvider(widget.host, widget.provider, uid, packageName)) {
+ // Apps hosting the AppWidget get to bind to a remote view service in the provider.
+ return true;
+ }
+ if (mContext.checkCallingPermission(android.Manifest.permission.BIND_APPWIDGET)
+ == PackageManager.PERMISSION_GRANTED) {
+ // Apps that can bind have access to all appWidgetIds.
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isParentOrProfile(int parentId, int profileId) {
+ if (parentId == profileId) {
+ return true;
+ }
+ return getProfileParent(profileId) == parentId;
+ }
+
+ public boolean isProviderInCallerOrInProfileAndWhitelListed(String packageName,
+ int profileId) {
+ final int callerId = UserHandle.getCallingUserId();
+ if (profileId == callerId) {
+ return true;
+ }
+ final int parentId = getProfileParent(profileId);
+ if (parentId != callerId) {
+ return false;
+ }
+ return isProviderWhitelListed(packageName, profileId);
+ }
+
+ public boolean isProviderWhitelListed(String packageName, int profileId) {
+ DevicePolicyManagerInternal devicePolicyManager = LocalServices.getService(
+ DevicePolicyManagerInternal.class);
+
+ // If the policy manager is not available on the device we deny it all.
+ if (devicePolicyManager == null) {
+ return false;
+ }
+
+ List<String> crossProfilePackages = devicePolicyManager
+ .getCrossProfileWidgetProviders(profileId);
+
+ return crossProfilePackages.contains(packageName);
+ }
+
+ public int getProfileParent(int profileId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ UserInfo parent = mUserManager.getProfileParent(profileId);
+ if (parent != null) {
+ return parent.getUserHandle().getIdentifier();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return UNKNOWN_USER_ID;
+ }
+
+ public int getGroupParent(int profileId) {
+ final int parentId = mSecurityPolicy.getProfileParent(profileId);
+ return (parentId != UNKNOWN_USER_ID) ? parentId : profileId;
+ }
+
+ public boolean isHostInPackageForUid(Host host, int uid, String packageName) {
+ if (UserHandle.getAppId(uid) == Process.myUid()) {
+ // For a host that's in the system process, ignore the user id.
+ return UserHandle.isSameApp(host.id.uid, uid)
+ && host.id.packageName.equals(packageName);
+ } else {
+ return host.id.uid == uid
+ && host.id.packageName.equals(packageName);
+ }
+ }
+
+ public boolean isProviderInPackageForUid(Provider provider, int uid,
+ String packageName) {
+ // Packages providing the AppWidget have access to it.
+ return provider != null && provider.id.uid == uid
+ && provider.id.componentName.getPackageName().equals(packageName);
+ }
+
+ public boolean isHostAccessingProvider(Host host, Provider provider, int uid,
+ String packageName) {
+ // The host creates a package context to bind to remote views service in the provider.
+ return host.id.uid == uid && provider != null
+ && provider.id.componentName.getPackageName().equals(packageName);
+ }
+
+ private boolean isProfileEnabled(int profileId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ UserInfo userInfo = mUserManager.getUserInfo(profileId);
+ if (userInfo == null || !userInfo.isEnabled()) {
+ return false;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return true;
+ }
+ }
+
+ private static final class Provider {
+ ProviderId id;
+ AppWidgetProviderInfo info;
+ ArrayList<Widget> widgets = new ArrayList<>();
+ PendingIntent broadcast;
+ boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
+
+ int tag = TAG_UNDEFINED; // for use while saving state (the index)
+
+ public int getUserId() {
+ return UserHandle.getUserId(id.uid);
+ }
+
+ public boolean isInPackageForUser(String packageName, int userId) {
+ return getUserId() == userId
+ && id.componentName.getPackageName().equals(packageName);
+ }
+
+ // is there an instance of this provider hosted by the given app?
+ public boolean hostedByPackageForUser(String packageName, int userId) {
+ final int N = widgets.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = widgets.get(i);
+ if (packageName.equals(widget.host.id.packageName)
+ && widget.host.getUserId() == userId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Provider{" + id + (zombie ? " Z" : "") + '}';
+ }
+ }
+
+ private static final class ProviderId {
+ final int uid;
+ final ComponentName componentName;
+
+ private ProviderId(int uid, ComponentName componentName) {
+ this.uid = uid;
+ this.componentName = componentName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ProviderId other = (ProviderId) obj;
+ if (uid != other.uid) {
+ return false;
+ }
+ if (componentName == null) {
+ if (other.componentName != null) {
+ return false;
+ }
+ } else if (!componentName.equals(other.componentName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = uid;
+ result = 31 * result + ((componentName != null)
+ ? componentName.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ProviderId{user:" + UserHandle.getUserId(uid) + ", app:"
+ + UserHandle.getAppId(uid) + ", cmp:" + componentName + '}';
+ }
+ }
+
+ private static final class Host {
+ HostId id;
+ ArrayList<Widget> widgets = new ArrayList<>();
+ IAppWidgetHost callbacks;
+ boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
+
+ int tag = TAG_UNDEFINED; // for use while saving state (the index)
+
+ public int getUserId() {
+ return UserHandle.getUserId(id.uid);
+ }
+
+ public boolean isInPackageForUser(String packageName, int userId) {
+ return getUserId() == userId && id.packageName.equals(packageName);
+ }
+
+ private boolean hostsPackageForUser(String pkg, int userId) {
+ final int N = widgets.size();
+ for (int i = 0; i < N; i++) {
+ Provider provider = widgets.get(i).provider;
+ if (provider != null && provider.getUserId() == userId && provider.info != null
+ && pkg.equals(provider.info.provider.getPackageName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Host{" + id + (zombie ? " Z" : "") + '}';
+ }
+ }
+
+ private static final class HostId {
+ final int uid;
+ final int hostId;
+ final String packageName;
+
+ public HostId(int uid, int hostId, String packageName) {
+ this.uid = uid;
+ this.hostId = hostId;
+ this.packageName = packageName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ HostId other = (HostId) obj;
+ if (uid != other.uid) {
+ return false;
+ }
+ if (hostId != other.hostId) {
+ return false;
+ }
+ if (packageName == null) {
+ if (other.packageName != null) {
+ return false;
+ }
+ } else if (!packageName.equals(other.packageName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = uid;
+ result = 31 * result + hostId;
+ result = 31 * result + ((packageName != null)
+ ? packageName.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "HostId{user:" + UserHandle.getUserId(uid) + ", app:"
+ + UserHandle.getAppId(uid) + ", hostId:" + hostId
+ + ", pkg:" + packageName + '}';
+ }
+ }
+
+ private static final class Widget {
+ int appWidgetId;
+ int restoredId; // tracking & remapping any restored state
+ Provider provider;
+ RemoteViews views;
+ Bundle options;
+ Host host;
+
+ @Override
+ public String toString() {
+ return "AppWidgetId{" + appWidgetId + ':' + host + ':' + provider + '}';
+ }
+ }
+
+ /**
+ * 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).
+ */
+ private static final class ServiceConnectionProxy implements ServiceConnection {
+ private final IRemoteViewsAdapterConnection mConnectionCb;
+
+ ServiceConnectionProxy(IBinder connectionCb) {
+ mConnectionCb = IRemoteViewsAdapterConnection.Stub
+ .asInterface(connectionCb);
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ mConnectionCb.onServiceConnected(service);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error passing service interface", re);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ disconnect();
+ }
+
+ public void disconnect() {
+ try {
+ mConnectionCb.onServiceDisconnected();
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error clearing service interface", re);
+ }
+ }
+ }
+
+ private class LoadedWidgetState {
+ final Widget widget;
+ final int hostTag;
+ final int providerTag;
+
+ public LoadedWidgetState(Widget widget, int hostTag, int providerTag) {
+ this.widget = widget;
+ this.hostTag = hostTag;
+ this.providerTag = providerTag;
+ }
+ }
+
+ private final class SaveStateRunnable implements Runnable {
+ final int mUserId;
+
+ public SaveStateRunnable(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(mUserId);
+ saveStateLocked(mUserId);
+ }
+ }
+ }
+
+ /**
+ * This class encapsulates the backup and restore logic for a user group state.
+ */
+ private final class BackupRestoreController {
+ private static final String TAG = "BackupRestoreController";
+
+ private static final boolean DEBUG = true;
+
+ // Version of backed-up widget state.
+ private static final int WIDGET_STATE_VERSION = 2;
+
+ // We need to make sure to wipe the pre-restore widget state only once for
+ // a given package. Keep track of what we've done so far here; the list is
+ // cleared at the start of every system restore pass, but preserved through
+ // any install-time restore operations.
+ private final HashSet<String> mPrunedApps = new HashSet<>();
+
+ private final HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider =
+ new HashMap<>();
+ private final HashMap<Host, ArrayList<RestoreUpdateRecord>> mUpdatesByHost =
+ new HashMap<>();
+
+ public List<String> getWidgetParticipants(int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "Getting widget participants for user: " + userId);
+ }
+
+ HashSet<String> packages = new HashSet<>();
+ synchronized (mLock) {
+ final int N = mWidgets.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = mWidgets.get(i);
+
+ // Skip cross-user widgets.
+ if (!isProviderAndHostInUser(widget, userId)) {
+ continue;
+ }
+
+ packages.add(widget.host.id.packageName);
+ Provider provider = widget.provider;
+ if (provider != null) {
+ packages.add(provider.id.componentName.getPackageName());
+ }
+ }
+ }
+ return new ArrayList<>(packages);
+ }
+
+ public byte[] getWidgetState(String backedupPackage, int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "Getting widget state for user: " + userId);
+ }
+
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ synchronized (mLock) {
+ // Preflight: if this app neither hosts nor provides any live widgets
+ // we have no work to do.
+ if (!packageNeedsWidgetBackupLocked(backedupPackage, userId)) {
+ return null;
+ }
+
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, "ws"); // widget state
+ out.attribute(null, "version", String.valueOf(WIDGET_STATE_VERSION));
+ out.attribute(null, "pkg", backedupPackage);
+
+ // Remember all the providers that are currently hosted or published
+ // by this package: that is, all of the entities related to this app
+ // which will need to be told about id remapping.
+ int index = 0;
+ int N = mProviders.size();
+ for (int i = 0; i < N; i++) {
+ Provider provider = mProviders.get(i);
+
+ if (!provider.widgets.isEmpty()
+ && (provider.isInPackageForUser(backedupPackage, userId)
+ || provider.hostedByPackageForUser(backedupPackage, userId))) {
+ provider.tag = index;
+ serializeProvider(out, provider);
+ index++;
+ }
+ }
+
+ N = mHosts.size();
+ index = 0;
+ for (int i = 0; i < N; i++) {
+ Host host = mHosts.get(i);
+
+ if (!host.widgets.isEmpty()
+ && (host.isInPackageForUser(backedupPackage, userId)
+ || host.hostsPackageForUser(backedupPackage, userId))) {
+ host.tag = index;
+ serializeHost(out, host);
+ index++;
+ }
+ }
+
+ // All widget instances involving this package,
+ // either as host or as provider
+ N = mWidgets.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = mWidgets.get(i);
+
+ Provider provider = widget.provider;
+ if (widget.host.isInPackageForUser(backedupPackage, userId)
+ || (provider != null
+ && provider.isInPackageForUser(backedupPackage, userId))) {
+ serializeAppWidget(out, widget);
+ }
+ }
+
+ out.endTag(null, "ws");
+ out.endDocument();
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to save widget state for " + backedupPackage);
+ return null;
+ }
+ }
+
+ return stream.toByteArray();
+ }
+
+ public void restoreStarting(int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "Restore starting for user: " + userId);
+ }
+
+ synchronized (mLock) {
+ // We're starting a new "system" restore operation, so any widget restore
+ // state that we see from here on is intended to replace the current
+ // widget configuration of any/all of the affected apps.
+ mPrunedApps.clear();
+ mUpdatesByProvider.clear();
+ mUpdatesByHost.clear();
+ }
+ }
+
+ public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "Restoring widget state for user:" + userId
+ + " package: " + packageName);
+ }
+
+ ByteArrayInputStream stream = new ByteArrayInputStream(restoredState);
+ try {
+ // Providers mentioned in the widget dataset by ordinal
+ ArrayList<Provider> restoredProviders = new ArrayList<>();
+
+ // Hosts mentioned in the widget dataset by ordinal
+ ArrayList<Host> restoredHosts = new ArrayList<>();
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+
+ synchronized (mLock) {
+ int type;
+ do {
+ type = parser.next();
+ if (type == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if ("ws".equals(tag)) {
+ String version = parser.getAttributeValue(null, "version");
+
+ final int versionNumber = Integer.parseInt(version);
+ if (versionNumber > WIDGET_STATE_VERSION) {
+ Slog.w(TAG, "Unable to process state version " + version);
+ return;
+ }
+
+ // TODO: fix up w.r.t. canonical vs current package names
+ String pkg = parser.getAttributeValue(null, "pkg");
+ if (!packageName.equals(pkg)) {
+ Slog.w(TAG, "Package mismatch in ws");
+ return;
+ }
+ } else if ("p".equals(tag)) {
+ String pkg = parser.getAttributeValue(null, "pkg");
+ String cl = parser.getAttributeValue(null, "cl");
+
+ // hostedProviders index will match 'p' attribute in widget's
+ // entry in the xml file being restored
+ // If there's no live entry for this provider, add an inactive one
+ // so that widget IDs referring to them can be properly allocated
+
+ // Backup and resotre only for the parent profile.
+ ComponentName componentName = new ComponentName(pkg, cl);
+
+ Provider p = findProviderLocked(componentName, userId);
+ if (p == null) {
+ p = new Provider();
+ p.id = new ProviderId(UNKNOWN_UID, componentName);
+ p.info = new AppWidgetProviderInfo();
+ p.info.provider = componentName;
+ p.zombie = true;
+ mProviders.add(p);
+ }
+ if (DEBUG) {
+ Slog.i(TAG, " provider " + p.id);
+ }
+ restoredProviders.add(p);
+ } else if ("h".equals(tag)) {
+ // The host app may not yet exist on the device. If it's here we
+ // just use the existing Host entry, otherwise we create a
+ // placeholder whose uid will be fixed up at PACKAGE_ADDED time.
+ String pkg = parser.getAttributeValue(null, "pkg");
+
+ final int uid = getUidForPackage(pkg, userId);
+ final int hostId = Integer.parseInt(
+ parser.getAttributeValue(null, "id"), 16);
+
+ HostId id = new HostId(uid, hostId, pkg);
+ Host h = lookupOrAddHostLocked(id);
+ restoredHosts.add(h);
+
+ if (DEBUG) {
+ Slog.i(TAG, " host[" + restoredHosts.size()
+ + "]: {" + h.id + "}");
+ }
+ } else if ("g".equals(tag)) {
+ int restoredId = Integer.parseInt(
+ parser.getAttributeValue(null, "id"), 16);
+ int hostIndex = Integer.parseInt(
+ parser.getAttributeValue(null, "h"), 16);
+ Host host = restoredHosts.get(hostIndex);
+ Provider p = null;
+ String prov = parser.getAttributeValue(null, "p");
+ if (prov != null) {
+ // could have been null if the app had allocated an id
+ // but not yet established a binding under that id
+ int which = Integer.parseInt(prov, 16);
+ p = restoredProviders.get(which);
+ }
+
+ // We'll be restoring widget state for both the host and
+ // provider sides of this widget ID, so make sure we are
+ // beginning from a clean slate on both fronts.
+ pruneWidgetStateLocked(host.id.packageName, userId);
+ if (p != null) {
+ pruneWidgetStateLocked(p.id.componentName.getPackageName(),
+ userId);
+ }
+
+ // Have we heard about this ancestral widget instance before?
+ Widget id = findRestoredWidgetLocked(restoredId, host, p);
+ if (id == null) {
+ id = new Widget();
+ id.appWidgetId = incrementAndGetAppWidgetIdLocked(userId);
+ id.restoredId = restoredId;
+ id.options = parseWidgetIdOptions(parser);
+ id.host = host;
+ id.host.widgets.add(id);
+ id.provider = p;
+ if (id.provider != null) {
+ id.provider.widgets.add(id);
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "New restored id " + restoredId
+ + " now " + id);
+ }
+ mWidgets.add(id);
+ }
+ if (id.provider.info != null) {
+ stashProviderRestoreUpdateLocked(id.provider,
+ restoredId, id.appWidgetId);
+ } else {
+ Slog.w(TAG, "Missing provider for restored widget " + id);
+ }
+ stashHostRestoreUpdateLocked(id.host, restoredId, id.appWidgetId);
+
+ if (DEBUG) {
+ Slog.i(TAG, " instance: " + restoredId
+ + " -> " + id.appWidgetId
+ + " :: p=" + id.provider);
+ }
+ }
+ }
+ } while (type != XmlPullParser.END_DOCUMENT);
+
+ // We've updated our own bookkeeping. We'll need to notify the hosts and
+ // providers about the changes, but we can't do that yet because the restore
+ // target is not necessarily fully live at this moment. Set aside the
+ // information for now; the backup manager will call us once more at the
+ // end of the process when all of the targets are in a known state, and we
+ // will update at that point.
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Slog.w(TAG, "Unable to restore widget state for " + packageName);
+ } finally {
+ saveGroupStateAsync(userId);
+ }
+ }
+
+ // Called once following the conclusion of a restore operation. This is when we
+ // send out updates to apps involved in widget-state restore telling them about
+ // the new widget ID space.
+ public void restoreFinished(int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "restoreFinished for " + userId);
+ }
+
+ final UserHandle userHandle = new UserHandle(userId);
+ synchronized (mLock) {
+ // Build the providers' broadcasts and send them off
+ Set<Map.Entry<Provider, ArrayList<RestoreUpdateRecord>>> providerEntries
+ = mUpdatesByProvider.entrySet();
+ for (Map.Entry<Provider, ArrayList<RestoreUpdateRecord>> e : providerEntries) {
+ // For each provider there's a list of affected IDs
+ Provider provider = e.getKey();
+ ArrayList<RestoreUpdateRecord> updates = e.getValue();
+ final int pending = countPendingUpdates(updates);
+ if (DEBUG) {
+ Slog.i(TAG, "Provider " + provider + " pending: " + pending);
+ }
+ if (pending > 0) {
+ int[] oldIds = new int[pending];
+ int[] newIds = new int[pending];
+ final int N = updates.size();
+ int nextPending = 0;
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = updates.get(i);
+ if (!r.notified) {
+ r.notified = true;
+ oldIds[nextPending] = r.oldId;
+ newIds[nextPending] = r.newId;
+ nextPending++;
+ if (DEBUG) {
+ Slog.i(TAG, " " + r.oldId + " => " + r.newId);
+ }
+ }
+ }
+ sendWidgetRestoreBroadcastLocked(
+ AppWidgetManager.ACTION_APPWIDGET_RESTORED,
+ provider, null, oldIds, newIds, userHandle);
+ }
+ }
+
+ // same thing per host
+ Set<Map.Entry<Host, ArrayList<RestoreUpdateRecord>>> hostEntries
+ = mUpdatesByHost.entrySet();
+ for (Map.Entry<Host, ArrayList<RestoreUpdateRecord>> e : hostEntries) {
+ Host host = e.getKey();
+ if (host.id.uid != UNKNOWN_UID) {
+ ArrayList<RestoreUpdateRecord> updates = e.getValue();
+ final int pending = countPendingUpdates(updates);
+ if (DEBUG) {
+ Slog.i(TAG, "Host " + host + " pending: " + pending);
+ }
+ if (pending > 0) {
+ int[] oldIds = new int[pending];
+ int[] newIds = new int[pending];
+ final int N = updates.size();
+ int nextPending = 0;
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = updates.get(i);
+ if (!r.notified) {
+ r.notified = true;
+ oldIds[nextPending] = r.oldId;
+ newIds[nextPending] = r.newId;
+ nextPending++;
+ if (DEBUG) {
+ Slog.i(TAG, " " + r.oldId + " => " + r.newId);
+ }
+ }
+ }
+ sendWidgetRestoreBroadcastLocked(
+ AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED,
+ null, host, oldIds, newIds, userHandle);
+ }
+ }
+ }
+ }
+ }
+
+ private Provider findProviderLocked(ComponentName componentName, int userId) {
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ if (provider.getUserId() == userId
+ && provider.id.componentName.equals(componentName)) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ private Widget findRestoredWidgetLocked(int restoredId, Host host, Provider p) {
+ if (DEBUG) {
+ Slog.i(TAG, "Find restored widget: id=" + restoredId
+ + " host=" + host + " provider=" + p);
+ }
+
+ if (p == null || host == null) {
+ return null;
+ }
+
+ final int N = mWidgets.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = mWidgets.get(i);
+ if (widget.restoredId == restoredId
+ && widget.host.id.equals(host.id)
+ && widget.provider.id.equals(p.id)) {
+ if (DEBUG) {
+ Slog.i(TAG, " Found at " + i + " : " + widget);
+ }
+ return widget;
+ }
+ }
+ return null;
+ }
+
+ private boolean packageNeedsWidgetBackupLocked(String packageName, int userId) {
+ int N = mWidgets.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = mWidgets.get(i);
+
+ // Skip cross-user widgets.
+ if (!isProviderAndHostInUser(widget, userId)) {
+ continue;
+ }
+
+ if (widget.host.isInPackageForUser(packageName, userId)) {
+ // this package is hosting widgets, so it knows widget IDs.
+ return true;
+ }
+
+ Provider provider = widget.provider;
+ if (provider != null && provider.isInPackageForUser(packageName, userId)) {
+ // someone is hosting this app's widgets, so it knows widget IDs.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void stashProviderRestoreUpdateLocked(Provider provider, int oldId, int newId) {
+ ArrayList<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider);
+ if (r == null) {
+ r = new ArrayList<>();
+ mUpdatesByProvider.put(provider, r);
+ } else {
+ // don't duplicate
+ if (alreadyStashed(r, oldId, newId)) {
+ if (DEBUG) {
+ Slog.i(TAG, "ID remap " + oldId + " -> " + newId
+ + " already stashed for " + provider);
+ }
+ return;
+ }
+ }
+ r.add(new RestoreUpdateRecord(oldId, newId));
+ }
+
+ private boolean alreadyStashed(ArrayList<RestoreUpdateRecord> stash,
+ final int oldId, final int newId) {
+ final int N = stash.size();
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = stash.get(i);
+ if (r.oldId == oldId && r.newId == newId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void stashHostRestoreUpdateLocked(Host host, int oldId, int newId) {
+ ArrayList<RestoreUpdateRecord> r = mUpdatesByHost.get(host);
+ if (r == null) {
+ r = new ArrayList<>();
+ mUpdatesByHost.put(host, r);
+ } else {
+ if (alreadyStashed(r, oldId, newId)) {
+ if (DEBUG) {
+ Slog.i(TAG, "ID remap " + oldId + " -> " + newId
+ + " already stashed for " + host);
+ }
+ return;
+ }
+ }
+ r.add(new RestoreUpdateRecord(oldId, newId));
+ }
+
+ private void sendWidgetRestoreBroadcastLocked(String action, Provider provider,
+ Host host, int[] oldIds, int[] newIds, UserHandle userHandle) {
+ Intent intent = new Intent(action);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, oldIds);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, newIds);
+ if (provider != null) {
+ intent.setComponent(provider.info.provider);
+ sendBroadcastAsUser(intent, userHandle);
+ }
+ if (host != null) {
+ intent.setComponent(null);
+ intent.setPackage(host.id.packageName);
+ intent.putExtra(AppWidgetManager.EXTRA_HOST_ID, host.id.hostId);
+ sendBroadcastAsUser(intent, userHandle);
+ }
+ }
+
+ // We're restoring widget state for 'pkg', so we start by wiping (a) all widget
+ // instances that are hosted by that app, and (b) all instances in other hosts
+ // for which 'pkg' is the provider. We assume that we'll be restoring all of
+ // these hosts & providers, so will be reconstructing a correct live state.
+ private void pruneWidgetStateLocked(String pkg, int userId) {
+ if (!mPrunedApps.contains(pkg)) {
+ if (DEBUG) {
+ Slog.i(TAG, "pruning widget state for restoring package " + pkg);
+ }
+ for (int i = mWidgets.size() - 1; i >= 0; i--) {
+ Widget widget = mWidgets.get(i);
+
+ Host host = widget.host;
+ Provider provider = widget.provider;
+
+ if (host.hostsPackageForUser(pkg, userId)
+ || (provider != null && provider.isInPackageForUser(pkg, userId))) {
+ // 'pkg' is either the host or the provider for this instances,
+ // so we tear it down in anticipation of it (possibly) being
+ // reconstructed due to the restore
+ host.widgets.remove(widget);
+ provider.widgets.remove(widget);
+ unbindAppWidgetRemoteViewsServicesLocked(widget);
+ mWidgets.remove(i);
+ }
+ }
+ mPrunedApps.add(pkg);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "already pruned " + pkg + ", continuing normally");
+ }
+ }
+ }
+
+ private boolean isProviderAndHostInUser(Widget widget, int userId) {
+ // Backup only widgets hosted or provided by the owner profile.
+ return widget.host.getUserId() == userId && (widget.provider == null
+ || widget.provider.getUserId() == userId);
+ }
+
+ private Bundle parseWidgetIdOptions(XmlPullParser parser) {
+ Bundle options = new Bundle();
+ String minWidthString = parser.getAttributeValue(null, "min_width");
+ if (minWidthString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
+ Integer.parseInt(minWidthString, 16));
+ }
+ String minHeightString = parser.getAttributeValue(null, "min_height");
+ if (minHeightString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
+ Integer.parseInt(minHeightString, 16));
+ }
+ String maxWidthString = parser.getAttributeValue(null, "max_width");
+ if (maxWidthString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
+ Integer.parseInt(maxWidthString, 16));
+ }
+ String maxHeightString = parser.getAttributeValue(null, "max_height");
+ if (maxHeightString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
+ Integer.parseInt(maxHeightString, 16));
+ }
+ String categoryString = parser.getAttributeValue(null, "host_category");
+ if (categoryString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ Integer.parseInt(categoryString, 16));
+ }
+ return options;
+ }
+
+ private int countPendingUpdates(ArrayList<RestoreUpdateRecord> updates) {
+ int pending = 0;
+ final int N = updates.size();
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = updates.get(i);
+ if (!r.notified) {
+ pending++;
+ }
+ }
+ return pending;
+ }
+
+ // Accumulate a list of updates that affect the given provider for a final
+ // coalesced notification broadcast once restore is over.
+ private class RestoreUpdateRecord {
+ public int oldId;
+ public int newId;
+ public boolean notified;
+
+ public RestoreUpdateRecord(int theOldId, int theNewId) {
+ oldId = theOldId;
+ newId = theNewId;
+ notified = false;
}
}
}
-}
+} \ No newline at end of file