diff options
author | Svetoslav <svetoslavganov@google.com> | 2014-07-16 15:12:03 -0700 |
---|---|---|
committer | Svetoslav Ganov <svetoslavganov@google.com> | 2014-08-05 20:57:20 +0000 |
commit | 976e8bd2017d0263216c62111454438cc0f130e3 (patch) | |
tree | 5cf592fb85841f9e41d3bf6b43422641c3609ab2 /services/appwidget/java/com/android | |
parent | c79eabcd3c6306bb2ec75f9584b79e661f265127 (diff) | |
download | frameworks_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/java/com/android')
-rw-r--r-- | services/appwidget/java/com/android/server/appwidget/AppWidgetService.java | 401 | ||||
-rw-r--r-- | services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java | 5003 |
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 |