diff options
24 files changed, 4096 insertions, 2651 deletions
diff --git a/api/current.txt b/api/current.txt index 3c5135a..5e557c6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5382,6 +5382,7 @@ package android.app.admin { public class DevicePolicyManager { method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int); + method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName); method public void addUserRestriction(android.content.ComponentName, java.lang.String); method public void clearCrossProfileIntentFilters(android.content.ComponentName); @@ -5398,6 +5399,7 @@ package android.app.admin { method public boolean getBlockUninstall(android.content.ComponentName, java.lang.String); method public boolean getCameraDisabled(android.content.ComponentName); method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName); + method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName); method public int getCurrentFailedPasswordAttempts(); method public int getKeyguardDisabledFeatures(android.content.ComponentName); method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName); @@ -5433,6 +5435,7 @@ package android.app.admin { method public void lockNow(); method public void registerPrivateKeyAccessListener(android.content.ComponentName, android.content.ComponentName); method public void removeActiveAdmin(android.content.ComponentName); + method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); method public boolean removeUser(android.content.ComponentName, android.os.UserHandle); method public boolean resetPassword(java.lang.String, int); method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); @@ -5738,6 +5741,7 @@ package android.appwidget { method protected android.appwidget.AppWidgetHostView onCreateView(android.content.Context, int, android.appwidget.AppWidgetProviderInfo); method protected void onProviderChanged(int, android.appwidget.AppWidgetProviderInfo); method protected void onProvidersChanged(); + method public final void startAppWidgetConfigureActivityForResult(android.app.Activity, android.content.Intent, int); method public void startListening(); method public void stopListening(); } @@ -5760,10 +5764,12 @@ package android.appwidget { public class AppWidgetManager { method public boolean bindAppWidgetIdIfAllowed(int, android.content.ComponentName); method public boolean bindAppWidgetIdIfAllowed(int, android.content.ComponentName, android.os.Bundle); + method public boolean bindAppWidgetIdIfAllowed(int, android.os.UserHandle, android.content.ComponentName, android.os.Bundle); method public int[] getAppWidgetIds(android.content.ComponentName); method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(int); method public android.os.Bundle getAppWidgetOptions(int); method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders(); + method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfiles(android.os.UserHandle[]); method public static android.appwidget.AppWidgetManager getInstance(android.content.Context); method public void notifyAppWidgetViewDataChanged(int[], int); method public void notifyAppWidgetViewDataChanged(int, int); @@ -5788,6 +5794,7 @@ package android.appwidget { field public static final java.lang.String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds"; field public static final java.lang.String EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions"; field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider"; + field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER_PROFILE = "appWidgetProviderProfile"; field public static final java.lang.String EXTRA_CUSTOM_EXTRAS = "customExtras"; field public static final java.lang.String EXTRA_CUSTOM_INFO = "customInfo"; field public static final java.lang.String EXTRA_HOST_ID = "hostId"; @@ -5816,6 +5823,10 @@ package android.appwidget { ctor public AppWidgetProviderInfo(android.os.Parcel); method public android.appwidget.AppWidgetProviderInfo clone(); method public int describeContents(); + method public final android.os.UserHandle getProfile(); + method public final android.graphics.drawable.Drawable loadIcon(android.content.Context, int); + method public final java.lang.String loadLabel(android.content.pm.PackageManager); + method public final android.graphics.drawable.Drawable loadPreviewImage(android.content.Context, int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final int RESIZE_BOTH = 3; // 0x3 @@ -5827,15 +5838,15 @@ package android.appwidget { field public static final int WIDGET_CATEGORY_RECENTS = 4; // 0x4 field public int autoAdvanceViewId; field public android.content.ComponentName configure; - field public int icon; + field public deprecated int icon; field public int initialKeyguardLayout; field public int initialLayout; - field public java.lang.String label; + field public deprecated java.lang.String label; field public int minHeight; field public int minResizeHeight; field public int minResizeWidth; field public int minWidth; - field public int previewImage; + field public deprecated int previewImage; field public android.content.ComponentName provider; field public int resizeMode; field public int updatePeriodMillis; @@ -7281,6 +7292,7 @@ package android.content { field public static final java.lang.String ACCOUNT_SERVICE = "account"; field public static final java.lang.String ACTIVITY_SERVICE = "activity"; field public static final java.lang.String ALARM_SERVICE = "alarm"; + field public static final java.lang.String APPWIDGET_SERVICE = "appwidget"; field public static final java.lang.String APP_OPS_SERVICE = "appops"; field public static final java.lang.String AUDIO_SERVICE = "audio"; field public static final java.lang.String BATTERY_SERVICE = "batterymanager"; diff --git a/cmds/appwidget/src/com/android/commands/appwidget/AppWidget.java b/cmds/appwidget/src/com/android/commands/appwidget/AppWidget.java index dd45d39..5ea7352 100644 --- a/cmds/appwidget/src/com/android/commands/appwidget/AppWidget.java +++ b/cmds/appwidget/src/com/android/commands/appwidget/AppWidget.java @@ -150,7 +150,7 @@ public class AppWidget { IBinder binder = ServiceManager.getService(Context.APPWIDGET_SERVICE); IAppWidgetService appWidgetService = IAppWidgetService.Stub.asInterface(binder); try { - appWidgetService.setBindAppWidgetPermission(mPackageName, mGranted, mUserId); + appWidgetService.setBindAppWidgetPermission(mPackageName, mUserId, mGranted); } catch (RemoteException re) { re.printStackTrace(); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 5f606a6..4cf8cb4 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -18,10 +18,12 @@ package android.app; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageStatsManager; +import android.appwidget.AppWidgetManager; import android.os.Build; import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; +import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.policy.PolicyManager; import com.android.internal.util.Preconditions; @@ -148,7 +150,6 @@ import android.app.trust.TrustManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; -import com.android.internal.appwidget.IAppWidgetService.Stub; import com.android.internal.os.IDropBoxManagerService; import com.android.internal.telecomm.ITelecommService; @@ -771,6 +772,12 @@ class ContextImpl extends Context { return new MediaProjectionManager(ctx); } }); + + registerService(APPWIDGET_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(APPWIDGET_SERVICE); + return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b)); + }}); } static ContextImpl getImpl(Context context) { @@ -2100,6 +2107,25 @@ class ContextImpl extends Context { } @Override + public Context createApplicationContext(ApplicationInfo application, int flags) + throws NameNotFoundException { + LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), + flags | CONTEXT_REGISTER_PACKAGE); + if (pi != null) { + final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED; + ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, + new UserHandle(UserHandle.getUserId(application.uid)), restricted, + mDisplay, mOverrideConfiguration); + if (c.mResources != null) { + return c; + } + } + + throw new PackageManager.NameNotFoundException( + "Application package " + application.packageName + " not found"); + } + + @Override public Context createPackageContext(String packageName, int flags) throws NameNotFoundException { return createPackageContextAsUser(packageName, flags, diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 21525bc..797a0a0 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1707,28 +1707,6 @@ public class Notification implements Parcelable } } - /** {@hide} */ - public void setUser(UserHandle user) { - if (user.getIdentifier() == UserHandle.USER_ALL) { - user = UserHandle.OWNER; - } - if (tickerView != null) { - tickerView.setUser(user); - } - if (contentView != null) { - contentView.setUser(user); - } - if (bigContentView != null) { - bigContentView.setUser(user); - } - if (headsUpContentView != null) { - headsUpContentView.setUser(user); - } - if (publicVersion != null) { - publicVersion.setUser(user); - } - } - /** * @hide */ diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 82d9d1d..5920923 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -20,6 +20,7 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.app.Activity; +import android.app.admin.IDevicePolicyManager; import android.content.AbstractRestrictionsProvider; import android.content.ComponentName; import android.content.Context; @@ -40,7 +41,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.security.Credentials; -import android.service.trust.TrustAgentService; import android.util.Log; import com.android.org.conscrypt.TrustedCertificateStore; @@ -57,6 +57,7 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -3126,4 +3127,85 @@ public class DevicePolicyManager { } return false; } + + /** + * Called by the profile owner to enable widget providers from a given package + * to be available in the parent profile. As a result the user will be able to + * add widgets from the white-listed package running under the profile to a widget + * host which runs under the device owner, for example the home screen. Note that + * a package may have zero or more provider components, where each component + * provides a different widget type. + * <p> + * <strong>Note:</strong> By default no widget provider package is white-listed. + * </p> + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The package from which widget providers are white-listed. + * @return Whether the package was added. + * + * @see #removeCrossProfileWidgetProvider(android.content.ComponentName, String) + * @see #getCrossProfileWidgetProviders(android.content.ComponentName) + */ + public boolean addCrossProfileWidgetProvider(ComponentName admin, String packageName) { + if (mService != null) { + try { + return mService.addCrossProfileWidgetProvider(admin, packageName); + } catch (RemoteException re) { + Log.w(TAG, "Error calling addCrossProfileWidgetProvider", re); + } + } + return false; + } + + /** + * Called by the profile owner to disable widget providers from a given package + * to be available in the parent profile. For this method to take effect the + * package should have been added via {@link #addCrossProfileWidgetProvider( + * android.content.ComponentName, String)}. + * <p> + * <strong>Note:</strong> By default no widget provider package is white-listed. + * </p> + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The package from which widget providers are no longer + * white-listed. + * @return Whether the package was removed. + * + * @see #addCrossProfileWidgetProvider(android.content.ComponentName, String) + * @see #getCrossProfileWidgetProviders(android.content.ComponentName) + */ + public boolean removeCrossProfileWidgetProvider(ComponentName admin, String packageName) { + if (mService != null) { + try { + return mService.removeCrossProfileWidgetProvider(admin, packageName); + } catch (RemoteException re) { + Log.w(TAG, "Error calling removeCrossProfileWidgetProvider", re); + } + } + return false; + } + + /** + * Called by the profile owner to query providers from which packages are + * available in the parent profile. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return The white-listed package list. + * + * @see #addCrossProfileWidgetProvider(android.content.ComponentName, String) + * @see #removeCrossProfileWidgetProvider(android.content.ComponentName, String) + */ + public List<String> getCrossProfileWidgetProviders(ComponentName admin) { + if (mService != null) { + try { + List<String> providers = mService.getCrossProfileWidgetProviders(admin); + if (providers != null) { + return providers; + } + } catch (RemoteException re) { + Log.w(TAG, "Error calling getCrossProfileWidgetProviders", re); + } + } + return Collections.emptyList(); + } } diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java new file mode 100644 index 0000000..edd8199 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import java.util.List; + +/** + * Device policy manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class DevicePolicyManagerInternal { + + /** + * Gets the packages whose widget providers are white-listed to be + * available in the parent user. + * + * @param profileId The profile id. + * @return The list of packages if such or empty list if there are + * no white-listed packages or the profile id is not a managed + * profile. + */ + public abstract List<String> getCrossProfileWidgetProviders(int profileId); +} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 699323d..c6fe4f9 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -182,4 +182,7 @@ interface IDevicePolicyManager { void setTrustAgentFeaturesEnabled(in ComponentName admin, in ComponentName agent, in List<String> features, int userId); List<String> getTrustAgentFeaturesEnabled(in ComponentName admin, in ComponentName agent, int userId); + boolean addCrossProfileWidgetProvider(in ComponentName admin, String packageName); + boolean removeCrossProfileWidgetProvider(in ComponentName admin, String packageName); + List<String> getCrossProfileWidgetProviders(in ComponentName admin); } diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index 84d3835..e7b68f5 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -19,7 +19,11 @@ package android.appwidget; import java.util.ArrayList; import java.util.HashMap; +import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -61,32 +65,30 @@ public class AppWidgetHost { private OnClickHandler mOnClickHandler; class Callbacks extends IAppWidgetHost.Stub { - public void updateAppWidget(int appWidgetId, RemoteViews views, int userId) { + public void updateAppWidget(int appWidgetId, RemoteViews views) { if (isLocalBinder() && views != null) { views = views.clone(); - views.setUser(new UserHandle(userId)); } - Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, userId, views); + Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views); msg.sendToTarget(); } - public void providerChanged(int appWidgetId, AppWidgetProviderInfo info, int userId) { + public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) { if (isLocalBinder() && info != null) { info = info.clone(); } Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED, - appWidgetId, userId, info); + appWidgetId, 0, info); msg.sendToTarget(); } - public void providersChanged(int userId) { - Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED, userId, 0); - msg.sendToTarget(); + public void providersChanged() { + mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget(); } - public void viewDataChanged(int appWidgetId, int viewId, int userId) { + public void viewDataChanged(int appWidgetId, int viewId) { Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED, - appWidgetId, viewId, userId); + appWidgetId, viewId); msg.sendToTarget(); } } @@ -99,7 +101,7 @@ public class AppWidgetHost { public void handleMessage(Message msg) { switch (msg.what) { case HANDLE_UPDATE: { - updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj, msg.arg2); + updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj); break; } case HANDLE_PROVIDER_CHANGED: { @@ -111,7 +113,7 @@ public class AppWidgetHost { break; } case HANDLE_VIEW_DATA_CHANGED: { - viewDataChanged(msg.arg1, msg.arg2, (Integer) msg.obj); + viewDataChanged(msg.arg1, msg.arg2); break; } } @@ -151,25 +153,20 @@ public class AppWidgetHost { public void startListening() { int[] updatedIds; ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>(); - - final int userId = mContext.getUserId(); try { if (mPackageName == null) { mPackageName = mContext.getPackageName(); } - updatedIds = sService.startListening( - mCallbacks, mPackageName, mHostId, updatedViews, userId); + updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId, + updatedViews); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); } final int N = updatedIds.length; - for (int i=0; i<N; i++) { - if (updatedViews.get(i) != null) { - updatedViews.get(i).setUser(new UserHandle(userId)); - } - updateAppWidgetView(updatedIds[i], updatedViews.get(i), userId); + for (int i = 0; i < N; i++) { + updateAppWidgetView(updatedIds[i], updatedViews.get(i)); } } @@ -179,7 +176,7 @@ public class AppWidgetHost { */ public void stopListening() { try { - sService.stopListening(mHostId, mContext.getUserId()); + sService.stopListening(mPackageName, mHostId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -200,7 +197,7 @@ public class AppWidgetHost { if (mPackageName == null) { mPackageName = mContext.getPackageName(); } - return sService.allocateAppWidgetId(mPackageName, mHostId, mContext.getUserId()); + return sService.allocateAppWidgetId(mPackageName, mHostId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -208,18 +205,39 @@ public class AppWidgetHost { } /** - * Get a appWidgetId for a host in the given package. + * Starts an app widget provider configure activity for result on behalf of the caller. + * Use this method if the provider is in another profile as you are not allowed to start + * an activity in another profile. The provided intent must have action {@link + * AppWidgetManager#ACTION_APPWIDGET_CONFIGURE}. The widget id must be specified by + * the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} extra. The provider configure + * activity must be specified as the component name of the intent via {@link + * Intent#setComponent(android.content.ComponentName)}. The user profile under which + * the provider operates is specified via the {@link + * AppWidgetManager#EXTRA_APPWIDGET_PROVIDER_PROFILE} extra. If a user profile is + * not provided, the current user is used. Finally, you can optionally provide a + * request code that is returned in {@link Activity#onActivityResult(int, int, + * android.content.Intent)}. * - * @return a appWidgetId - * @hide + * @param activity The activity from which to start the configure one. + * @param intent Properly populated intent for launching the configuration activity. + * @param requestCode Optional request code retuned with the result. + * + * @throws android.content.ActivityNotFoundException If the activity is not found. + * + * @see AppWidgetProviderInfo#getProfile() */ - public static int allocateAppWidgetIdForPackage(int hostId, int userId, String packageName) { - checkCallerIsSystem(); + public final void startAppWidgetConfigureActivityForResult(Activity activity, Intent intent, + int requestCode) { try { - if (sService == null) { - bindService(); + IntentSender intentSender = sService.createAppWidgetConfigIntentSender( + mContext.getPackageName(), intent); + if (intentSender != null) { + activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0); + } else { + throw new ActivityNotFoundException(); } - return sService.allocateAppWidgetId(packageName, hostId, userId); + } catch (IntentSender.SendIntentException e) { + throw new ActivityNotFoundException(); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); } @@ -235,20 +253,12 @@ public class AppWidgetHost { if (sService == null) { bindService(); } - return sService.getAppWidgetIdsForHost(mHostId, mContext.getUserId()); + return sService.getAppWidgetIdsForHost(mContext.getPackageName(), mHostId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); } } - private static void checkCallerIsSystem() { - int uid = Process.myUid(); - if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) { - return; - } - throw new SecurityException("Disallowed call for uid " + uid); - } - private boolean isLocalBinder() { return Process.myPid() == Binder.getCallingPid(); } @@ -260,7 +270,7 @@ public class AppWidgetHost { synchronized (mViews) { mViews.remove(appWidgetId); try { - sService.deleteAppWidgetId(appWidgetId, mContext.getUserId()); + sService.deleteAppWidgetId(mContext.getPackageName(), appWidgetId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -269,22 +279,6 @@ public class AppWidgetHost { } /** - * Stop listening to changes for this AppWidget. - * @hide - */ - public static void deleteAppWidgetIdForSystem(int appWidgetId, int userId) { - checkCallerIsSystem(); - try { - if (sService == null) { - bindService(); - } - sService.deleteAppWidgetId(appWidgetId, userId); - } catch (RemoteException e) { - throw new RuntimeException("system server dead?", e); - } - } - - /** * Remove all records about this host from the AppWidget manager. * <ul> * <li>Call this when initializing your database, as it might be because of a data wipe.</li> @@ -294,7 +288,7 @@ public class AppWidgetHost { */ public void deleteHost() { try { - sService.deleteHost(mHostId, mContext.getUserId()); + sService.deleteHost(mContext.getPackageName(), mHostId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -310,16 +304,8 @@ public class AppWidgetHost { * </ul> */ public static void deleteAllHosts() { - deleteAllHosts(UserHandle.myUserId()); - } - - /** - * Private method containing a userId - * @hide - */ - public static void deleteAllHosts(int userId) { try { - sService.deleteAllHosts(userId); + sService.deleteAllHosts(); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -332,9 +318,7 @@ public class AppWidgetHost { */ public final AppWidgetHostView createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { - final int userId = mContext.getUserId(); AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget); - view.setUserId(userId); view.setOnClickHandler(mOnClickHandler); view.setAppWidget(appWidgetId, appWidget); synchronized (mViews) { @@ -342,10 +326,7 @@ public class AppWidgetHost { } RemoteViews views; try { - views = sService.getAppWidgetViews(appWidgetId, userId); - if (views != null) { - views.setUser(new UserHandle(mContext.getUserId())); - } + views = sService.getAppWidgetViews(mPackageName, appWidgetId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); } @@ -397,7 +378,7 @@ public class AppWidgetHost { // Does nothing } - void updateAppWidgetView(int appWidgetId, RemoteViews views, int userId) { + void updateAppWidgetView(int appWidgetId, RemoteViews views) { AppWidgetHostView v; synchronized (mViews) { v = mViews.get(appWidgetId); @@ -407,7 +388,7 @@ public class AppWidgetHost { } } - void viewDataChanged(int appWidgetId, int viewId, int userId) { + void viewDataChanged(int appWidgetId, int viewId) { AppWidgetHostView v; synchronized (mViews) { v = mViews.get(appWidgetId); diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 700bba8..1ff476e 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -87,7 +87,6 @@ public class AppWidgetHostView extends FrameLayout { Bitmap mOld; Paint mOldPaint = new Paint(); private OnClickHandler mOnClickHandler; - private UserHandle mUser; /** * Create a host view. Uses default fade animations. @@ -115,17 +114,11 @@ public class AppWidgetHostView extends FrameLayout { public AppWidgetHostView(Context context, int animationIn, int animationOut) { super(context); mContext = context; - mUser = Process.myUserHandle(); // We want to segregate the view ids within AppWidgets to prevent // problems when those ids collide with view ids in the AppWidgetHost. setIsRootNamespace(true); } - /** @hide */ - public void setUserId(int userId) { - mUser = new UserHandle(userId); - } - /** * Pass the given handler to RemoteViews when updating this widget. Unless this * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)} @@ -380,7 +373,7 @@ public class AppWidgetHostView extends FrameLayout { } else { // Prepare a local reference to the remote Context so we're ready to // inflate any requested LayoutParams. - mRemoteContext = getRemoteContext(remoteViews); + mRemoteContext = getRemoteContext(); int layoutId = remoteViews.getLayoutId(); // If our stale view has been prepared to match active, and the new @@ -466,17 +459,14 @@ public class AppWidgetHostView extends FrameLayout { * Build a {@link Context} cloned into another package name, usually for the * purposes of reading remote resources. */ - private Context getRemoteContext(RemoteViews views) { - // Bail if missing package name - final String packageName = views.getPackage(); - if (packageName == null) return mContext; - + private Context getRemoteContext() { try { // Return if cloned successfully, otherwise default - return mContext.createPackageContextAsUser(packageName, Context.CONTEXT_RESTRICTED, - mUser); + return mContext.createApplicationContext( + mInfo.providerInfo.applicationInfo, + Context.CONTEXT_RESTRICTED); } catch (NameNotFoundException e) { - Log.e(TAG, "Package name " + packageName + " not found"); + Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found"); return mContext; } } @@ -548,8 +538,7 @@ public class AppWidgetHostView extends FrameLayout { try { if (mInfo != null) { - Context theirContext = mContext.createPackageContextAsUser( - mInfo.provider.getPackageName(), Context.CONTEXT_RESTRICTED, mUser); + Context theirContext = getRemoteContext(); mRemoteContext = theirContext; LayoutInflater inflater = (LayoutInflater) theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -572,8 +561,6 @@ public class AppWidgetHostView extends FrameLayout { } else { Log.w(TAG, "can't inflate defaultView because mInfo is missing"); } - } catch (PackageManager.NameNotFoundException e) { - exception = e; } catch (RuntimeException e) { exception = e; } diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index e5bf7d0..e5d1d95 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -21,8 +21,8 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.util.DisplayMetrics; import android.util.TypedValue; @@ -30,9 +30,8 @@ import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetService; -import java.lang.ref.WeakReference; +import java.util.Collections; import java.util.List; -import java.util.WeakHashMap; /** * Updates AppWidget state; gets information about installed AppWidget providers and other @@ -45,7 +44,6 @@ import java.util.WeakHashMap; * </div> */ public class AppWidgetManager { - static final String TAG = "AppWidgetManager"; /** * Activity action to launch from your {@link AppWidgetHost} activity when you want to @@ -72,9 +70,9 @@ public class AppWidgetManager { * <p> * When you receive the result from the AppWidget pick activity, if the resultCode is * {@link android.app.Activity#RESULT_OK}, an AppWidget has been selected. You should then - * check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its configuration - * activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you should delete - * the appWidgetId. + * check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its + * configuration activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you + * should delete the appWidgetId. * * @see #ACTION_APPWIDGET_CONFIGURE */ @@ -103,6 +101,12 @@ public class AppWidgetManager { * <td>The BroadcastReceiver that will be the AppWidget provider for this AppWidget. * </td> * </tr> + * <tr> + * <td>{@link #EXTRA_APPWIDGET_PROVIDER_PROFILE}</td> + * <td>An optional handle to a user profile under which runs the provider + * for this AppWidget. + * </td> + * </tr> * </table> * * <p> @@ -119,8 +123,7 @@ public class AppWidgetManager { * {@link android.app.Activity#RESULT_OK}, the AppWidget has been bound. You should then * check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its * configuration activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you - * should delete - * the appWidgetId. + * should delete the appWidgetId. * * @see #ACTION_APPWIDGET_CONFIGURE * @@ -130,7 +133,8 @@ public class AppWidgetManager { /** * Sent when it is time to configure your AppWidget while it is being added to a host. * This action is not sent as a broadcast to the AppWidget provider, but as a startActivity - * to the activity specified in the {@link AppWidgetProviderInfo AppWidgetProviderInfo meta-data}. + * to the activity specified in the {@link AppWidgetProviderInfo AppWidgetProviderInfo + * meta-data}. * * <p> * The intent will contain the following extras: @@ -145,7 +149,8 @@ public class AppWidgetManager { * {@link android.app.Activity#setResult Activity.setResult()}, the AppWidget will be added, * and you will receive an {@link #ACTION_APPWIDGET_UPDATE} broadcast for this AppWidget. * If you return {@link android.app.Activity#RESULT_CANCELED}, the host will cancel the add - * and not display this AppWidget, and you will receive a {@link #ACTION_APPWIDGET_DELETED} broadcast. + * and not display this AppWidget, and you will receive a {@link #ACTION_APPWIDGET_DELETED} + * broadcast. */ public static final String ACTION_APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE"; @@ -188,7 +193,9 @@ public class AppWidgetManager { /** * An intent extra which points to a bundle of extra information for a particular widget id. - * In particular this bundle can contain EXTRA_APPWIDGET_WIDTH and EXTRA_APPWIDGET_HEIGHT. + * In particular this bundle can contain {@link #OPTION_APPWIDGET_MIN_WIDTH}, + * {@link #OPTION_APPWIDGET_MIN_HEIGHT}, {@link #OPTION_APPWIDGET_MAX_WIDTH}, + * {@link #OPTION_APPWIDGET_MAX_HEIGHT}. */ public static final String EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions"; @@ -203,11 +210,19 @@ public class AppWidgetManager { /** * An intent extra that contains the component name of a AppWidget provider. * <p> - * The value will be an ComponentName. + * The value will be an {@link android.content.ComponentName}. */ public static final String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider"; /** + * An intent extra that contains the user handle of the profile under + * which an AppWidget provider is registered. + * <p> + * The value will be a {@link android.os.UserHandle}. + */ + public static final String EXTRA_APPWIDGET_PROVIDER_PROFILE = "appWidgetProviderProfile"; + + /** * An intent extra to pass to the AppWidget picker containing a {@link java.util.List} of * {@link AppWidgetProviderInfo} objects to mix in to the list of AppWidgets that are * installed. (This is how the launcher shows the search widget). @@ -295,12 +310,12 @@ public class AppWidgetManager { public static final String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED"; /** - * Sent when an instance of an AppWidget is removed from the last host. + * Sent when the last AppWidget of this provider is removed from the last host. * * <p class="note">This is a protected intent that can only be sent * by the system. * - * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context) + * @see AppWidgetProvider#onEnabled AppWidgetProvider.onDisabled(Context context) */ public static final String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED"; @@ -403,46 +418,36 @@ public class AppWidgetManager { */ public static final String META_DATA_APPWIDGET_PROVIDER = "android.appwidget.provider"; - static WeakHashMap<Context, WeakReference<AppWidgetManager>> sManagerCache = - new WeakHashMap<Context, WeakReference<AppWidgetManager>>(); - static IAppWidgetService sService; + private final String mPackageName; - Context mContext; + private final IAppWidgetService mService; - private DisplayMetrics mDisplayMetrics; + private final DisplayMetrics mDisplayMetrics; /** * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context * Context} object. */ public static AppWidgetManager getInstance(Context context) { - synchronized (sManagerCache) { - if (sService == null) { - IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE); - sService = IAppWidgetService.Stub.asInterface(b); - } - - WeakReference<AppWidgetManager> ref = sManagerCache.get(context); - AppWidgetManager result = null; - if (ref != null) { - result = ref.get(); - } - if (result == null) { - result = new AppWidgetManager(context); - sManagerCache.put(context, new WeakReference<AppWidgetManager>(result)); - } - return result; - } + return (AppWidgetManager) context.getSystemService(Context.APPWIDGET_SERVICE); } - private AppWidgetManager(Context context) { - mContext = context; + /** + * Creates a new instance. + * + * @param context The current context in which to operate. + * @param service The backing system service. + * @hide + */ + public AppWidgetManager(Context context, IAppWidgetService service) { + mPackageName = context.getPackageName(); + mService = service; mDisplayMetrics = context.getResources().getDisplayMetrics(); } /** * Set the RemoteViews to use for the specified appWidgetIds. - * + * <p> * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should * contain a complete representation of the widget. For performing partial widget updates, see * {@link #partiallyUpdateAppWidget(int[], RemoteViews)}. @@ -456,12 +461,15 @@ public class AppWidgetManager { * The total Bitmap memory used by the RemoteViews object cannot exceed that required to * fill the screen 1.5 times, ie. (screen width x screen height x 4 x 1.5) bytes. * - * @param appWidgetIds The AppWidget instances for which to set the RemoteViews. - * @param views The RemoteViews object to show. + * @param appWidgetIds The AppWidget instances for which to set the RemoteViews. + * @param views The RemoteViews object to show. */ public void updateAppWidget(int[] appWidgetIds, RemoteViews views) { + if (mService == null) { + return; + } try { - sService.updateAppWidgetIds(appWidgetIds, views, mContext.getUserId()); + mService.updateAppWidgetIds(mPackageName, appWidgetIds, views); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -470,18 +478,21 @@ public class AppWidgetManager { /** * Update the extras for a given widget instance. - * + * <p> * The extras can be used to embed additional information about this widget to be accessed * by the associated widget's AppWidgetProvider. * * @see #getAppWidgetOptions(int) * - * @param appWidgetId The AppWidget instances for which to set the RemoteViews. - * @param options The options to associate with this widget + * @param appWidgetId The AppWidget instances for which to set the RemoteViews. + * @param options The options to associate with this widget */ public void updateAppWidgetOptions(int appWidgetId, Bundle options) { + if (mService == null) { + return; + } try { - sService.updateAppWidgetOptions(appWidgetId, options, mContext.getUserId()); + mService.updateAppWidgetOptions(mPackageName, appWidgetId, options); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -490,18 +501,21 @@ public class AppWidgetManager { /** * Get the extras associated with a given widget instance. - * + * <p> * The extras can be used to embed additional information about this widget to be accessed * by the associated widget's AppWidgetProvider. * * @see #updateAppWidgetOptions(int, Bundle) * - * @param appWidgetId The AppWidget instances for which to set the RemoteViews. - * @return The options associated with the given widget instance. + * @param appWidgetId The AppWidget instances for which to set the RemoteViews. + * @return The options associated with the given widget instance. */ public Bundle getAppWidgetOptions(int appWidgetId) { + if (mService == null) { + return Bundle.EMPTY; + } try { - return sService.getAppWidgetOptions(appWidgetId, mContext.getUserId()); + return mService.getAppWidgetOptions(mPackageName, appWidgetId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -510,7 +524,7 @@ public class AppWidgetManager { /** * Set the RemoteViews to use for the specified appWidgetId. - * + * <p> * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should * contain a complete representation of the widget. For performing partial widget updates, see * {@link #partiallyUpdateAppWidget(int, RemoteViews)}. @@ -528,12 +542,15 @@ public class AppWidgetManager { * @param views The RemoteViews object to show. */ public void updateAppWidget(int appWidgetId, RemoteViews views) { + if (mService == null) { + return; + } updateAppWidget(new int[] { appWidgetId }, views); } /** * Perform an incremental update or command on the widget(s) specified by appWidgetIds. - * + * <p> * This update differs from {@link #updateAppWidget(int[], RemoteViews)} in that the * RemoteViews object which is passed is understood to be an incomplete representation of the * widget, and hence does not replace the cached representation of the widget. As of API @@ -556,8 +573,11 @@ public class AppWidgetManager { * @param views The RemoteViews object containing the incremental update / command. */ public void partiallyUpdateAppWidget(int[] appWidgetIds, RemoteViews views) { + if (mService == null) { + return; + } try { - sService.partiallyUpdateAppWidgetIds(appWidgetIds, views, mContext.getUserId()); + mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); } @@ -565,7 +585,7 @@ public class AppWidgetManager { /** * Perform an incremental update or command on the widget specified by appWidgetId. - * + * <p> * This update differs from {@link #updateAppWidget(int, RemoteViews)} in that the RemoteViews * object which is passed is understood to be an incomplete representation of the widget, and * hence is not cached by the AppWidgetService. Note that because these updates are not cached, @@ -588,6 +608,9 @@ public class AppWidgetManager { * @param views The RemoteViews object containing the incremental update / command. */ public void partiallyUpdateAppWidget(int appWidgetId, RemoteViews views) { + if (mService == null) { + return; + } partiallyUpdateAppWidget(new int[] { appWidgetId }, views); } @@ -605,8 +628,11 @@ public class AppWidgetManager { * @param views The RemoteViews object to show. */ public void updateAppWidget(ComponentName provider, RemoteViews views) { + if (mService == null) { + return; + } try { - sService.updateAppWidgetProvider(provider, views, mContext.getUserId()); + mService.updateAppWidgetProvider(provider, views); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -621,8 +647,11 @@ public class AppWidgetManager { * @param viewId The collection view id. */ public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { + if (mService == null) { + return; + } try { - sService.notifyAppWidgetViewDataChanged(appWidgetIds, viewId, mContext.getUserId()); + mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -637,37 +666,106 @@ public class AppWidgetManager { * @param viewId The collection view id. */ public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) { + if (mService == null) { + return; + } notifyAppWidgetViewDataChanged(new int[] { appWidgetId }, viewId); } /** + * Gets the AppWidget providers for the given user profiles. User profiles can only + * be the current user or a profile of the current user. For example, the current + * user may have a corporate profile. In this case the parent user profile has a + * child profile, the corporate one. + * + * @param profiles The profiles for which to get providers. Passing null is equivaled + * to passing only the current user handle. + * @return The intalled providers. + * + * @see android.os.Process#myUserHandle() + * @see android.os.UserManager#getUserProfiles() + */ + public List<AppWidgetProviderInfo> getInstalledProvidersForProfiles(UserHandle[] profiles) { + if (mService == null) { + return Collections.emptyList(); + } + return getInstalledProvidersForProfiles(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, + profiles); + } + + /** * Return a list of the AppWidget providers that are currently installed. */ public List<AppWidgetProviderInfo> getInstalledProviders() { - return getInstalledProviders(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN); + if (mService == null) { + return Collections.emptyList(); + } + return getInstalledProvidersForProfiles(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, + null); } /** - * Return a list of the AppWidget providers that are currently installed. + * Gets the AppWidget providers for the current user. * * @param categoryFilter Will only return providers which register as any of the specified * specified categories. See {@link AppWidgetProviderInfo#widgetCategory}. + * @return The intalled providers. + * + * @see android.os.Process#myUserHandle() + * @see android.os.UserManager#getUserProfiles() + * * @hide */ public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter) { + if (mService == null) { + return Collections.emptyList(); + } + return getInstalledProvidersForProfiles(categoryFilter, null); + } + + /** + * Gets the AppWidget providers for the given user profiles. User profiles can only + * be the current user or a profile of the current user. For example, the current + * user may have a corporate profile. In this case the parent user profile has a + * child profile, the corporate one. + * + * @param categoryFilter Will only return providers which register as any of the specified + * specified categories. See {@link AppWidgetProviderInfo#widgetCategory}. + * @param profiles Child profiles of the current user which to be queried. The user + * is itself also a profile. If null, the providers only for the current user + * are returned. + * @return The intalled providers. + * + * @see android.os.Process#myUserHandle() + * @see android.os.UserManager#getUserProfiles() + * + * @hide + */ + public List<AppWidgetProviderInfo> getInstalledProvidersForProfiles(int categoryFilter, + UserHandle[] profiles) { + if (mService == null) { + return Collections.emptyList(); + } + + int[] profileIds = null; + + if (profiles != null) { + final int profileCount = profiles.length; + profileIds = new int[profileCount]; + for (int i = 0; i < profileCount; i++) { + profileIds[i] = profiles[i].getIdentifier(); + } + } + try { - List<AppWidgetProviderInfo> providers = sService.getInstalledProviders(categoryFilter, - mContext.getUserId()); + List<AppWidgetProviderInfo> providers = mService.getInstalledProviders(categoryFilter, + profileIds); + if (providers == null) { + return Collections.emptyList(); + } for (AppWidgetProviderInfo info : providers) { // Converting complex to dp. - info.minWidth = - TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics); - info.minHeight = - TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics); - info.minResizeWidth = - TypedValue.complexToDimensionPixelSize(info.minResizeWidth, mDisplayMetrics); - info.minResizeHeight = - TypedValue.complexToDimensionPixelSize(info.minResizeHeight, mDisplayMetrics); + convertSizesToPixels(info); } return providers; } @@ -683,19 +781,14 @@ public class AppWidgetManager { * you don't have access to that appWidgetId, null is returned. */ public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { + if (mService == null) { + return null; + } try { - AppWidgetProviderInfo info = sService.getAppWidgetInfo(appWidgetId, - mContext.getUserId()); + AppWidgetProviderInfo info = mService.getAppWidgetInfo(mPackageName, appWidgetId); if (info != null) { // Converting complex to dp. - info.minWidth = - TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics); - info.minHeight = - TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics); - info.minResizeWidth = - TypedValue.complexToDimensionPixelSize(info.minResizeWidth, mDisplayMetrics); - info.minResizeHeight = - TypedValue.complexToDimensionPixelSize(info.minResizeHeight, mDisplayMetrics); + convertSizesToPixels(info); } return info; } @@ -717,12 +810,10 @@ public class AppWidgetManager { * @hide */ public void bindAppWidgetId(int appWidgetId, ComponentName provider) { - try { - sService.bindAppWidgetId(appWidgetId, provider, null, mContext.getUserId()); - } - catch (RemoteException e) { - throw new RuntimeException("system server dead?", e); + if (mService == null) { + return; } + bindAppWidgetId(appWidgetId, provider, null); } /** @@ -741,12 +832,10 @@ public class AppWidgetManager { * @hide */ public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) { - try { - sService.bindAppWidgetId(appWidgetId, provider, options, mContext.getUserId()); - } - catch (RemoteException e) { - throw new RuntimeException("system server dead?", e); + if (mService == null) { + return; } + bindAppWidgetIdIfAllowed(appWidgetId, Process.myUserHandle(), provider, options); } /** @@ -757,22 +846,16 @@ public class AppWidgetManager { * method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to * bind * - * @param appWidgetId The AppWidget instance for which to set the RemoteViews. + * @param appWidgetId The AppWidget id under which to bind the provider. * @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget * provider for this AppWidget. * @return true if this component has permission to bind the AppWidget */ public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider) { - if (mContext == null) { + if (mService == null) { return false; } - try { - return sService.bindAppWidgetIdIfAllowed( - mContext.getPackageName(), appWidgetId, provider, null, mContext.getUserId()); - } - catch (RemoteException e) { - throw new RuntimeException("system server dead?", e); - } + return bindAppWidgetIdIfAllowed(appWidgetId, UserHandle.myUserId(), provider, null); } /** @@ -783,7 +866,7 @@ public class AppWidgetManager { * method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to * bind * - * @param appWidgetId The AppWidget instance for which to set the RemoteViews. + * @param appWidgetId The AppWidget id under which to bind the provider. * @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget * provider for this AppWidget. * @param options Bundle containing options for the AppWidget. See also @@ -793,12 +876,52 @@ public class AppWidgetManager { */ public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider, Bundle options) { - if (mContext == null) { + if (mService == null) { + return false; + } + return bindAppWidgetIdIfAllowed(appWidgetId, UserHandle.myUserId(), provider, options); + } + + /** + * Set the provider for a given appWidgetId if the caller has a permission. + * <p> + * <strong>Note:</strong> You need the {@link android.Manifest.permission#BIND_APPWIDGET} + * permission or the user must have enabled binding widgets always for your component. + * Should be used by apps that host widgets. If this method returns false, call {@link + * #ACTION_APPWIDGET_BIND} to request permission to bind. + * </p> + * + * @param appWidgetId The AppWidget id under which to bind the provider. + * @param user The user id in which the provider resides. + * @param provider The component name of the provider. + * @param options An optional Bundle containing options for the AppWidget. + * + * @return true if this component has permission to bind the AppWidget + */ + public boolean bindAppWidgetIdIfAllowed(int appWidgetId, UserHandle user, + ComponentName provider, Bundle options) { + if (mService == null) { + return false; + } + return bindAppWidgetIdIfAllowed(appWidgetId, user.getIdentifier(), provider, options); + } + + /** + * Query if a given package was granted permission by the user to bind app widgets + * + * <p class="note">You need the MODIFY_APPWIDGET_BIND_PERMISSIONS permission + * + * @param packageName The package for which the permission is being queried + * @param userId The user id of the user under which the package runs. + * @return true if the package was granted permission by the user to bind app widgets + * @hide + */ + public boolean hasBindAppWidgetPermission(String packageName, int userId) { + if (mService == null) { return false; } try { - return sService.bindAppWidgetIdIfAllowed(mContext.getPackageName(), appWidgetId, - provider, options, mContext.getUserId()); + return mService.hasBindAppWidgetPermission(packageName, userId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -815,8 +938,11 @@ public class AppWidgetManager { * @hide */ public boolean hasBindAppWidgetPermission(String packageName) { + if (mService == null) { + return false; + } try { - return sService.hasBindAppWidgetPermission(packageName, mContext.getUserId()); + return mService.hasBindAppWidgetPermission(packageName, UserHandle.myUserId()); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -828,13 +954,35 @@ public class AppWidgetManager { * * <p class="note">You need the MODIFY_APPWIDGET_BIND_PERMISSIONS permission * - * @param provider The package whose permission is being changed - * @param permission Whether to give the package permission to bind widgets + * @param packageName The package whose permission is being changed + * @param permission Whether to give the package permission to bind widgets + * * @hide */ public void setBindAppWidgetPermission(String packageName, boolean permission) { + if (mService == null) { + return; + } + setBindAppWidgetPermission(packageName, UserHandle.myUserId(), permission); + } + + /** + * Changes any user-granted permission for the given package to bind app widgets + * + * <p class="note">You need the MODIFY_APPWIDGET_BIND_PERMISSIONS permission + * + * @param packageName The package whose permission is being changed + * @param userId The user under which the package is running. + * @param permission Whether to give the package permission to bind widgets + * + * @hide + */ + public void setBindAppWidgetPermission(String packageName, int userId, boolean permission) { + if (mService == null) { + return; + } try { - sService.setBindAppWidgetPermission(packageName, permission, mContext.getUserId()); + mService.setBindAppWidgetPermission(packageName, userId, permission); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -847,18 +995,20 @@ public class AppWidgetManager { * The appWidgetId specified must already be bound to the calling AppWidgetHost via * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}. * + * @param packageName The package from which the binding is requested. * @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService. * @param intent The intent of the service which will be providing the data to the * RemoteViewsAdapter. * @param connection The callback interface to be notified when a connection is made or lost. - * @param userHandle The user to bind to. * @hide */ - public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection, - UserHandle userHandle) { + public void bindRemoteViewsService(String packageName, int appWidgetId, Intent intent, + IBinder connection) { + if (mService == null) { + return; + } try { - sService.bindRemoteViewsService(appWidgetId, intent, connection, - userHandle.getIdentifier()); + mService.bindRemoteViewsService(packageName, appWidgetId, intent, connection); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -871,15 +1021,18 @@ public class AppWidgetManager { * The appWidgetId specified muse already be bound to the calling AppWidgetHost via * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}. * + * @param packageName The package from which the binding is requested. * @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService. * @param intent The intent of the service which will be providing the data to the * RemoteViewsAdapter. - * @param userHandle The user to unbind from. * @hide */ - public void unbindRemoteViewsService(int appWidgetId, Intent intent, UserHandle userHandle) { + public void unbindRemoteViewsService(String packageName, int appWidgetId, Intent intent) { + if (mService == null) { + return; + } try { - sService.unbindRemoteViewsService(appWidgetId, intent, userHandle.getIdentifier()); + mService.unbindRemoteViewsService(packageName, appWidgetId, intent); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -894,12 +1047,40 @@ public class AppWidgetManager { * AppWidget provider to find appWidgetIds for. */ public int[] getAppWidgetIds(ComponentName provider) { + if (mService == null) { + return new int[0]; + } try { - return sService.getAppWidgetIds(provider, mContext.getUserId()); + return mService.getAppWidgetIds(provider); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); } } -} + private boolean bindAppWidgetIdIfAllowed(int appWidgetId, int profileId, + ComponentName provider, Bundle options) { + if (mService == null) { + return false; + } + try { + return mService.bindAppWidgetId(mPackageName, appWidgetId, + profileId, provider, options); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + private void convertSizesToPixels(AppWidgetProviderInfo info) { + // Converting complex to dp. + info.minWidth = TypedValue.complexToDimensionPixelSize(info.minWidth, + mDisplayMetrics); + info.minHeight = TypedValue.complexToDimensionPixelSize(info.minHeight, + mDisplayMetrics); + info.minResizeWidth = TypedValue.complexToDimensionPixelSize(info.minResizeWidth, + mDisplayMetrics); + info.minResizeHeight = TypedValue.complexToDimensionPixelSize(info.minResizeHeight, + mDisplayMetrics); + } +} diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index 8b9c7f0..e4dad5a 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -16,9 +16,17 @@ package android.appwidget; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.content.ComponentName; +import android.os.UserHandle; +import android.os.UserManager; /** * Describes the meta data for an installed AppWidget provider. The fields in this class @@ -145,21 +153,23 @@ public class AppWidgetProviderInfo implements Parcelable { public ComponentName configure; /** - * The label to display to the user in the AppWidget picker. If not supplied in the - * xml, the application label will be used. + * The label to display to the user in the AppWidget picker. * - * <p>This field corresponds to the <code>android:label</code> attribute in - * the <code><receiver></code> element in the AndroidManifest.xml file. + * @deprecated Use {@link #loadLabel(android.content.pm.PackageManager)}. */ + @Deprecated public String label; /** - * The icon to display for this AppWidget in the AppWidget picker. If not supplied in the + * The icon to display for this AppWidget in the AppWidget picker. If not supplied in the * xml, the application icon will be used. * * <p>This field corresponds to the <code>android:icon</code> attribute in * the <code><receiver></code> element in the AndroidManifest.xml file. + * + * @deprecated Use {@link #loadIcon(android.content.Context, int)}. */ + @Deprecated public int icon; /** @@ -176,7 +186,10 @@ public class AppWidgetProviderInfo implements Parcelable { * * <p>This field corresponds to the <code>android:previewImage</code> attribute in * the <code><receiver></code> element in the AndroidManifest.xml file. + * + * @deprecated User {@link #loadPreviewImage(android.content.Context, int)}. */ + @Deprecated public int previewImage; /** @@ -200,12 +213,17 @@ public class AppWidgetProviderInfo implements Parcelable { */ public int widgetCategory; + /** @hide */ + public ActivityInfo providerInfo; + public AppWidgetProviderInfo() { + } /** * Unflatten the AppWidgetProviderInfo from a parcel. */ + @SuppressWarnings("deprecation") public AppWidgetProviderInfo(Parcel in) { if (0 != in.readInt()) { this.provider = new ComponentName(in); @@ -226,8 +244,86 @@ public class AppWidgetProviderInfo implements Parcelable { this.autoAdvanceViewId = in.readInt(); this.resizeMode = in.readInt(); this.widgetCategory = in.readInt(); + this.providerInfo = in.readParcelable(null); + } + + /** + * Loads the localized label to display to the user in the AppWidget picker. + * + * @param packageManager Package manager instance for loading resources. + * @return The label for the current locale. + */ + public final String loadLabel(PackageManager packageManager) { + CharSequence label = providerInfo.loadLabel(packageManager); + if (label != null) { + return label.toString().trim(); + } + return null; + } + + /** + * Loads the icon to display for this AppWidget in the AppWidget picker. If not + * supplied in the xml, the application icon will be used. A client can optionally + * provide a desired density such as {@link android.util.DisplayMetrics#DENSITY_LOW} + * {@link android.util.DisplayMetrics#DENSITY_MEDIUM}, etc. If no density is + * provided, the density of the current display will be used. + * <p> + * The loaded icon corresponds to the <code>android:icon</code> attribute in + * the <code><receiver></code> element in the AndroidManifest.xml file. + * </p> + * <p> + * <strong>Note:</strong> If you care about widgets from different profiles, you + * should use this method to load the icon as the system will apply the correct + * badging when applicable, so the user knows which profile a widget comes from. + * </p> + * + * @param context Context for accessing resources. + * @param density The optional desired density as per + * {@link android.util.DisplayMetrics#densityDpi}. + * @return The potentially badged provider icon. + */ + public final Drawable loadIcon(Context context, int density) { + return loadDrawable(context, density, providerInfo.getIconResource()); } + /** + * Loads a preview of what the AppWidget will look like after it's configured. + * If not supplied, the AppWidget's icon will be used. A client can optionally + * provide a desired deinsity such as {@link android.util.DisplayMetrics#DENSITY_LOW} + * {@link android.util.DisplayMetrics#DENSITY_MEDIUM}, etc. If no density is + * provided, the density of the current display will be used. + * <p> + * The loaded image corresponds to the <code>android:previewImage</code> attribute + * in the <code><receiver></code> element in the AndroidManifest.xml file. + * </p> + * <p> + * <strong>Note:</strong> If you care about widgets from different profiles, you + * should use this method to load the preview image as the system will apply the + * correct badging when applicable, so the user knows which profile a previewed + * widget comes from. + * </p> + * + * @param context Context for accessing resources. + * @param density The optional desired density as per + * {@link android.util.DisplayMetrics#densityDpi}. + * @return The potentially badged widget preview image. + */ + @SuppressWarnings("deprecation") + public final Drawable loadPreviewImage(Context context, int density) { + return loadDrawable(context, density, previewImage); + } + + /** + * Gets the user profile in which the provider resides. + * + * @return The hosting user profile. + */ + public final UserHandle getProfile() { + return new UserHandle(UserHandle.getUserId(providerInfo.applicationInfo.uid)); + } + + @Override + @SuppressWarnings("deprecation") public void writeToParcel(android.os.Parcel out, int flags) { if (this.provider != null) { out.writeInt(1); @@ -254,9 +350,11 @@ public class AppWidgetProviderInfo implements Parcelable { out.writeInt(this.autoAdvanceViewId); out.writeInt(this.resizeMode); out.writeInt(this.widgetCategory); + out.writeParcelable(this.providerInfo, flags); } @Override + @SuppressWarnings("deprecation") public AppWidgetProviderInfo clone() { AppWidgetProviderInfo that = new AppWidgetProviderInfo(); that.provider = this.provider == null ? null : this.provider.clone(); @@ -273,7 +371,8 @@ public class AppWidgetProviderInfo implements Parcelable { that.previewImage = this.previewImage; that.autoAdvanceViewId = this.autoAdvanceViewId; that.resizeMode = this.resizeMode; - that.widgetCategory = this.widgetCategory; + that.widgetCategory = this.widgetCategory; + that.providerInfo = this.providerInfo; return that; } @@ -281,6 +380,33 @@ public class AppWidgetProviderInfo implements Parcelable { return 0; } + private Drawable loadDrawable(Context context, int density, int resourceId) { + try { + Resources resources = context.getPackageManager().getResourcesForApplication( + providerInfo.applicationInfo); + + final Drawable drawable; + if (resourceId > 0) { + if (density <= 0) { + density = context.getResources().getDisplayMetrics().densityDpi; + } + drawable = resources.getDrawableForDensity(resourceId, density); + } else { + drawable = providerInfo.loadIcon(context.getPackageManager()); + } + + if (drawable instanceof BitmapDrawable) { + UserManager userManager = (UserManager) context.getSystemService( + Context.USER_SERVICE); + return userManager.getBadgedDrawableForUser(drawable, getProfile()); + } + } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) { + /* ignore */ + } + + return null; + } + /** * Parcelable.Creator that instantiates AppWidgetProviderInfo objects */ @@ -299,6 +425,6 @@ public class AppWidgetProviderInfo implements Parcelable { }; public String toString() { - return "AppWidgetProviderInfo(provider=" + this.provider + ")"; + return "AppWidgetProviderInfo(" + getProfile() + '/' + provider + ')'; } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index f3a7b1c1..7417208 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2076,7 +2076,7 @@ public abstract class Context { CLIPBOARD_SERVICE, INPUT_METHOD_SERVICE, TEXT_SERVICES_MANAGER_SERVICE, - //@hide: APPWIDGET_SERVICE, + APPWIDGET_SERVICE, //@hide: BACKUP_SERVICE, DROPBOX_SERVICE, DEVICE_POLICY_SERVICE, @@ -2596,7 +2596,6 @@ public abstract class Context { * Use with {@link #getSystemService} to retrieve a * {@link android.appwidget.AppWidgetManager} for accessing AppWidgets. * - * @hide * @see #getSystemService */ public static final String APPWIDGET_SERVICE = "appwidget"; @@ -3306,6 +3305,14 @@ public abstract class Context { throws PackageManager.NameNotFoundException; /** + * Creates a context given an {@link android.content.pm.ApplicationInfo}. + * + * @hide + */ + public abstract Context createApplicationContext(ApplicationInfo application, + int flags) throws PackageManager.NameNotFoundException; + + /** * Get the userId associated with this context * @return user id * diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 4e1c4a7..ad7c350 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -666,6 +666,12 @@ public class ContextWrapper extends Context { } /** @hide */ + public Context createApplicationContext(ApplicationInfo application, + int flags) throws PackageManager.NameNotFoundException { + return mBase.createApplicationContext(application, flags); + } + + /** @hide */ @Override public int getUserId() { return mBase.getUserId(); diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index e5a5292..0eda692 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -63,7 +63,6 @@ public class StatusBarNotification implements Parcelable { this.score = score; this.notification = notification; this.user = user; - this.notification.setUser(user); this.postTime = postTime; this.key = key(); this.groupKey = groupKey(); @@ -83,7 +82,6 @@ public class StatusBarNotification implements Parcelable { this.score = in.readInt(); this.notification = new Notification(in); this.user = UserHandle.readFromParcel(in); - this.notification.setUser(this.user); this.postTime = in.readLong(); this.key = key(); this.groupKey = groupKey(); diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java index 5b80648..1716dbd 100644 --- a/core/java/android/widget/AnalogClock.java +++ b/core/java/android/widget/AnalogClock.java @@ -111,7 +111,15 @@ public class AnalogClock extends View { filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - getContext().registerReceiver(mIntentReceiver, filter, null, mHandler); + // OK, this is gross but needed. This class is supported by the + // remote views machanism and as a part of that the remote views + // can be inflated by a context for another user without the app + // having interact users permission - just for loading resources. + // For exmaple, when adding widgets from a user profile to the + // home screen. Therefore, we register the receiver as the current + // user not the one the context is for. + getContext().registerReceiverAsUser(mIntentReceiver, + android.os.Process.myUserHandle(), filter, null, mHandler); } // NOTE: It's safe to do these after registering the receiver since the receiver always runs diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 5c7a43b..1098fa2 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -17,6 +17,7 @@ package android.widget; import android.app.ActivityOptions; +import android.app.ActivityThread; import android.app.PendingIntent; import android.appwidget.AppWidgetHostView; import android.content.Context; @@ -73,11 +74,11 @@ public class RemoteViews implements Parcelable, Filter { static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId"; /** - * User that these views should be applied as. Requires - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} when - * crossing user boundaries. + * Application that hosts the remote views. + * + * @hide */ - private UserHandle mUser = android.os.Process.myUserHandle(); + private ApplicationInfo mApplication; /** * The package name of the package containing the layout @@ -275,9 +276,9 @@ public class RemoteViews implements Parcelable, Filter { /** * Merges the passed RemoteViews actions with this RemoteViews actions according to * action-specific merge rules. - * + * * @param newRv - * + * * @hide */ public void mergeRemoteViews(RemoteViews newRv) { @@ -1608,16 +1609,16 @@ public class RemoteViews implements Parcelable, Filter { int bpp = 4; if (c != null) { switch (c) { - case ALPHA_8: - bpp = 1; - break; - case RGB_565: - case ARGB_4444: - bpp = 2; - break; - case ARGB_8888: - bpp = 4; - break; + case ALPHA_8: + bpp = 1; + break; + case RGB_565: + case ARGB_4444: + bpp = 2; + break; + case ARGB_8888: + bpp = 4; + break; } } increment(b.getWidth() * b.getHeight() * bpp); @@ -1637,17 +1638,13 @@ public class RemoteViews implements Parcelable, Filter { mPackage = packageName; mLayoutId = layoutId; mBitmapCache = new BitmapCache(); + mApplication = ActivityThread.currentApplication().getApplicationInfo(); // setup the memory usage statistics mMemoryUsageCounter = new MemoryUsageCounter(); recalculateMemoryUsage(); } - /** {@hide} */ - public void setUser(UserHandle user) { - mUser = user; - } - private boolean hasLandscapeAndPortraitLayouts() { return (mLandscape != null) && (mPortrait != null); } @@ -1713,53 +1710,53 @@ public class RemoteViews implements Parcelable, Filter { for (int i=0; i<count; i++) { int tag = parcel.readInt(); switch (tag) { - case SetOnClickPendingIntent.TAG: - mActions.add(new SetOnClickPendingIntent(parcel)); - break; - case SetDrawableParameters.TAG: - mActions.add(new SetDrawableParameters(parcel)); - break; - case ReflectionAction.TAG: - mActions.add(new ReflectionAction(parcel)); - break; - case ViewGroupAction.TAG: - mActions.add(new ViewGroupAction(parcel, mBitmapCache)); - break; - case ReflectionActionWithoutParams.TAG: - mActions.add(new ReflectionActionWithoutParams(parcel)); - break; - case SetEmptyView.TAG: - mActions.add(new SetEmptyView(parcel)); - break; - case SetPendingIntentTemplate.TAG: - mActions.add(new SetPendingIntentTemplate(parcel)); - break; - case SetOnClickFillInIntent.TAG: - mActions.add(new SetOnClickFillInIntent(parcel)); - break; - case SetRemoteViewsAdapterIntent.TAG: - mActions.add(new SetRemoteViewsAdapterIntent(parcel)); - break; - case TextViewDrawableAction.TAG: - mActions.add(new TextViewDrawableAction(parcel)); - break; - case TextViewSizeAction.TAG: - mActions.add(new TextViewSizeAction(parcel)); - break; - case ViewPaddingAction.TAG: - mActions.add(new ViewPaddingAction(parcel)); - break; - case BitmapReflectionAction.TAG: - mActions.add(new BitmapReflectionAction(parcel)); - break; - case SetRemoteViewsAdapterList.TAG: - mActions.add(new SetRemoteViewsAdapterList(parcel)); - break; - case TextViewDrawableColorFilterAction.TAG: - mActions.add(new TextViewDrawableColorFilterAction(parcel)); - break; - default: - throw new ActionException("Tag " + tag + " not found"); + case SetOnClickPendingIntent.TAG: + mActions.add(new SetOnClickPendingIntent(parcel)); + break; + case SetDrawableParameters.TAG: + mActions.add(new SetDrawableParameters(parcel)); + break; + case ReflectionAction.TAG: + mActions.add(new ReflectionAction(parcel)); + break; + case ViewGroupAction.TAG: + mActions.add(new ViewGroupAction(parcel, mBitmapCache)); + break; + case ReflectionActionWithoutParams.TAG: + mActions.add(new ReflectionActionWithoutParams(parcel)); + break; + case SetEmptyView.TAG: + mActions.add(new SetEmptyView(parcel)); + break; + case SetPendingIntentTemplate.TAG: + mActions.add(new SetPendingIntentTemplate(parcel)); + break; + case SetOnClickFillInIntent.TAG: + mActions.add(new SetOnClickFillInIntent(parcel)); + break; + case SetRemoteViewsAdapterIntent.TAG: + mActions.add(new SetRemoteViewsAdapterIntent(parcel)); + break; + case TextViewDrawableAction.TAG: + mActions.add(new TextViewDrawableAction(parcel)); + break; + case TextViewSizeAction.TAG: + mActions.add(new TextViewSizeAction(parcel)); + break; + case ViewPaddingAction.TAG: + mActions.add(new ViewPaddingAction(parcel)); + break; + case BitmapReflectionAction.TAG: + mActions.add(new BitmapReflectionAction(parcel)); + break; + case SetRemoteViewsAdapterList.TAG: + mActions.add(new SetRemoteViewsAdapterList(parcel)); + break; + case TextViewDrawableColorFilterAction.TAG: + mActions.add(new TextViewDrawableColorFilterAction(parcel)); + break; + default: + throw new ActionException("Tag " + tag + " not found"); } } } @@ -1771,6 +1768,8 @@ public class RemoteViews implements Parcelable, Filter { mLayoutId = mPortrait.getLayoutId(); } + mApplication = parcel.readParcelable(null); + // setup the memory usage statistics mMemoryUsageCounter = new MemoryUsageCounter(); recalculateMemoryUsage(); @@ -2557,22 +2556,32 @@ public class RemoteViews implements Parcelable, Filter { } private Context prepareContext(Context context) { - Context c; - String packageName = mPackage; + if (mApplication != null) { + if (context.getUserId() == UserHandle.getUserId(mApplication.uid) + && context.getPackageName().equals(mApplication.packageName)) { + return context; + } + try { + return context.createApplicationContext(mApplication, + Context.CONTEXT_RESTRICTED); + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, "Package name " + mPackage + " not found"); + } + } - if (packageName != null) { + if (mPackage != null) { + if (context.getPackageName().equals(mPackage)) { + return context; + } try { - c = context.createPackageContextAsUser( - packageName, Context.CONTEXT_RESTRICTED, mUser); + return context.createPackageContext( + mPackage, Context.CONTEXT_RESTRICTED); } catch (NameNotFoundException e) { - Log.e(LOG_TAG, "Package name " + packageName + " not found"); - c = context; + Log.e(LOG_TAG, "Package name " + mPackage + " not found"); } - } else { - c = context; } - return c; + return context; } /** @@ -2629,6 +2638,8 @@ public class RemoteViews implements Parcelable, Filter { mLandscape.writeToParcel(dest, flags); mPortrait.writeToParcel(dest, flags); } + + dest.writeParcelable(mApplication, 0); } /** diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index bbe6f9e..5d21e0b 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -114,8 +114,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback // construction (happens when we have a cached FixedSizeRemoteViewsCache). private boolean mDataReady = false; - int mUserId; - /** * An interface for the RemoteAdapter to notify other classes when adapters * are actually connected to/disconnected from their actual services. @@ -159,9 +157,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback RemoteViewsAdapter adapter; final AppWidgetManager mgr = AppWidgetManager.getInstance(context); if ((adapter = mAdapter.get()) != null) { - checkInteractAcrossUsersPermission(context, adapter.mUserId); - mgr.bindRemoteViewsService(appWidgetId, intent, asBinder(), - new UserHandle(adapter.mUserId)); + mgr.bindRemoteViewsService(context.getPackageName(), appWidgetId, + intent, asBinder()); } else { Slog.w(TAG, "bind: adapter was null"); } @@ -179,9 +176,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback RemoteViewsAdapter adapter; final AppWidgetManager mgr = AppWidgetManager.getInstance(context); if ((adapter = mAdapter.get()) != null) { - checkInteractAcrossUsersPermission(context, adapter.mUserId); - mgr.unbindRemoteViewsService(appWidgetId, intent, - new UserHandle(adapter.mUserId)); + mgr.unbindRemoteViewsService(context.getPackageName(), appWidgetId, intent); } else { Slog.w(TAG, "unbind: adapter was null"); } @@ -796,12 +791,10 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback static class RemoteViewsCacheKey { final Intent.FilterComparison filter; final int widgetId; - final int userId; - RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId, int userId) { + RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId) { this.filter = filter; this.widgetId = widgetId; - this.userId = userId; } @Override @@ -810,29 +803,28 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback return false; } RemoteViewsCacheKey other = (RemoteViewsCacheKey) o; - return other.filter.equals(filter) && other.widgetId == widgetId - && other.userId == userId; + return other.filter.equals(filter) && other.widgetId == widgetId; } @Override public int hashCode() { - return (filter == null ? 0 : filter.hashCode()) ^ (widgetId << 2) ^ (userId << 10); + return (filter == null ? 0 : filter.hashCode()) ^ (widgetId << 2); } } - public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) { + public RemoteViewsAdapter(Context context, Intent intent, + RemoteAdapterConnectionCallback callback) { mContext = context; mIntent = intent; + mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1); + mLayoutInflater = LayoutInflater.from(context); if (mIntent == null) { throw new IllegalArgumentException("Non-null Intent must be specified."); } mRequestedViews = new RemoteViewsFrameLayoutRefSet(); - checkInteractAcrossUsersPermission(context, UserHandle.myUserId()); - mUserId = context.getUserId(); - // Strip the previously injected app widget id from service intent if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) { intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID); @@ -855,7 +847,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback mServiceConnection = new RemoteViewsAdapterServiceConnection(this); RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent), - mAppWidgetId, mUserId); + mAppWidgetId); synchronized(sCachedRemoteViewsCaches) { if (sCachedRemoteViewsCaches.containsKey(key)) { @@ -876,15 +868,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } } - private static void checkInteractAcrossUsersPermission(Context context, int userId) { - if (context.getUserId() != userId - && context.checkCallingOrSelfPermission(MULTI_USER_PERM) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Must have permission " + MULTI_USER_PERM - + " to inflate another user's widget"); - } - } - @Override protected void finalize() throws Throwable { try { @@ -906,7 +889,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback public void saveRemoteViewsCache() { final RemoteViewsCacheKey key = new RemoteViewsCacheKey( - new Intent.FilterComparison(mIntent), mAppWidgetId, mUserId); + new Intent.FilterComparison(mIntent), mAppWidgetId); synchronized(sCachedRemoteViewsCaches) { // If we already have a remove runnable posted for this key, remove it. @@ -1028,7 +1011,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback long itemId = 0; try { remoteViews = factory.getViewAt(position); - remoteViews.setUser(new UserHandle(mUserId)); itemId = factory.getItemId(position); } catch (RemoteException e) { Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage()); diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java index b152297..cf1f554 100644 --- a/core/java/android/widget/ViewFlipper.java +++ b/core/java/android/widget/ViewFlipper.java @@ -21,8 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.TypedArray; -import android.os.Handler; -import android.os.Message; +import android.os.*; import android.util.AttributeSet; import android.util.Log; import android.view.accessibility.AccessibilityEvent; @@ -90,7 +89,16 @@ public class ViewFlipper extends ViewAnimator { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_USER_PRESENT); - getContext().registerReceiver(mReceiver, filter, null, mHandler); + + // OK, this is gross but needed. This class is supported by the + // remote views machanism and as a part of that the remote views + // can be inflated by a context for another user without the app + // having interact users permission - just for loading resources. + // For exmaple, when adding widgets from a user profile to the + // home screen. Therefore, we register the receiver as the current + // user not the one the context is for. + getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(), + filter, null, mHandler); if (mAutoStart) { // Automatically start when requested diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl index 6d51d38..a7f7fe1 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl @@ -16,15 +16,16 @@ package com.android.internal.appwidget; +import android.content.pm.ApplicationInfo; import android.content.ComponentName; import android.appwidget.AppWidgetProviderInfo; import android.widget.RemoteViews; /** {@hide} */ oneway interface IAppWidgetHost { - void updateAppWidget(int appWidgetId, in RemoteViews views, int userId); - void providerChanged(int appWidgetId, in AppWidgetProviderInfo info, int userId); - void providersChanged(int userId); - void viewDataChanged(int appWidgetId, int viewId, int userId); + void updateAppWidget(int appWidgetId, in RemoteViews views); + void providerChanged(int appWidgetId, in AppWidgetProviderInfo info); + void providersChanged(); + void viewDataChanged(int appWidgetId, int viewId); } diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl index 5214dd9..9da1c9d 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl @@ -18,6 +18,8 @@ package com.android.internal.appwidget; import android.content.ComponentName; import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.ApplicationInfo; import android.appwidget.AppWidgetProviderInfo; import com.android.internal.appwidget.IAppWidgetHost; import android.os.Bundle; @@ -30,34 +32,37 @@ interface IAppWidgetService { // // for AppWidgetHost // - int[] startListening(IAppWidgetHost host, String packageName, int hostId, - out List<RemoteViews> updatedViews, int userId); - void stopListening(int hostId, int userId); - int allocateAppWidgetId(String packageName, int hostId, int userId); - void deleteAppWidgetId(int appWidgetId, int userId); - void deleteHost(int hostId, int userId); - void deleteAllHosts(int userId); - RemoteViews getAppWidgetViews(int appWidgetId, int userId); - int[] getAppWidgetIdsForHost(int hostId, int userId); + int[] startListening(IAppWidgetHost host, String callingPackage, int hostId, + out List<RemoteViews> updatedViews); + void stopListening(String callingPackage, int hostId); + int allocateAppWidgetId(String callingPackage, int hostId); + void deleteAppWidgetId(String callingPackage, int appWidgetId); + void deleteHost(String packageName, int hostId); + void deleteAllHosts(); + RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId); + int[] getAppWidgetIdsForHost(String callingPackage, int hostId); + IntentSender createAppWidgetConfigIntentSender(String callingPackage, in Intent intent); // // for AppWidgetManager // - void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views, int userId); - void updateAppWidgetOptions(int appWidgetId, in Bundle extras, int userId); - Bundle getAppWidgetOptions(int appWidgetId, int userId); - void partiallyUpdateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views, int userId); - void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views, int userId); - void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, int viewId, int userId); - List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int userId); - AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId, int userId); + void updateAppWidgetIds(String callingPackage, in int[] appWidgetIds, in RemoteViews views); + void updateAppWidgetOptions(String callingPackage, int appWidgetId, in Bundle extras); + Bundle getAppWidgetOptions(String callingPackage, int appWidgetId); + void partiallyUpdateAppWidgetIds(String callingPackage, in int[] appWidgetIds, + in RemoteViews views); + void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views); + void notifyAppWidgetViewDataChanged(String packageName, in int[] appWidgetIds, int viewId); + List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, + in int[] profileIds); + AppWidgetProviderInfo getAppWidgetInfo(String callingPackage, int appWidgetId); boolean hasBindAppWidgetPermission(in String packageName, int userId); - void setBindAppWidgetPermission(in String packageName, in boolean permission, int userId); - void bindAppWidgetId(int appWidgetId, in ComponentName provider, in Bundle options, int userId); - boolean bindAppWidgetIdIfAllowed(in String packageName, int appWidgetId, - in ComponentName provider, in Bundle options, int userId); - void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection, int userId); - void unbindRemoteViewsService(int appWidgetId, in Intent intent, int userId); - int[] getAppWidgetIds(in ComponentName provider, int userId); + void setBindAppWidgetPermission(in String packageName, int userId, in boolean permission); + boolean bindAppWidgetId(in String callingPackage, int appWidgetId, + int providerProfileId, in ComponentName providerComponent, in Bundle options); + void bindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent, + in IBinder connection); + void unbindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent); + int[] getAppWidgetIds(in ComponentName providerComponent); } 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 diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 49faf6b..abbb7a0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -29,6 +29,7 @@ import android.app.PendingIntent; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IDevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -71,7 +72,6 @@ import android.security.IKeyChainAliasCallback; import android.security.IKeyChainService; import android.security.KeyChain; import android.security.KeyChain.KeyChainConnection; -import android.service.trust.TrustAgentService; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -81,13 +81,13 @@ import android.util.Xml; import android.view.IWindowManager; import com.android.internal.R; -import com.android.internal.app.IAppOpsService; import com.android.internal.os.storage.ExternalStorageFormatter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; import com.android.org.conscrypt.TrustedCertificateStore; +import com.android.server.LocalServices; import com.android.server.SystemService; import org.xmlpull.v1.XmlPullParser; @@ -118,7 +118,6 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -294,6 +293,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ATTR_VALUE = "value"; private static final String TAG_PASSWORD_QUALITY = "password-quality"; private static final String TAG_POLICIES = "policies"; + private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS = + "cross-profile-widget-providers"; + private static final String TAG_PROVIDER = "provider"; final DeviceAdminInfo info; @@ -351,6 +353,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { String globalProxyExclusionList = null; HashMap<String, List<String>> trustAgentFeatures = new HashMap<String, List<String>>(); + List<String> crossProfileWidgetProviders; + ActiveAdmin(DeviceAdminInfo _info) { info = _info; } @@ -495,6 +499,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } out.endTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES); } + if (crossProfileWidgetProviders != null && !crossProfileWidgetProviders.isEmpty()) { + out.startTag(null, TAG_CROSS_PROFILE_WIDGET_PROVIDERS); + final int providerCount = crossProfileWidgetProviders.size(); + for (int i = 0; i < providerCount; i++) { + String provider = crossProfileWidgetProviders.get(i); + out.startTag(null, TAG_PROVIDER); + out.attribute(null, ATTR_VALUE, provider); + out.endTag(null, TAG_PROVIDER); + } + out.endTag(null, TAG_CROSS_PROFILE_WIDGET_PROVIDERS); + } } void readFromXml(XmlPullParser parser) @@ -576,6 +591,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { accountTypesWithManagementDisabled = readDisableAccountInfo(parser, tag); } else if (TAG_MANAGE_TRUST_AGENT_FEATURES.equals(tag)) { trustAgentFeatures = getAllTrustAgentFeatures(parser, tag); + } else if (TAG_CROSS_PROFILE_WIDGET_PROVIDERS.equals(tag)) { + crossProfileWidgetProviders = getCrossProfileWidgetProviders(parser, tag); } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); } @@ -645,6 +662,30 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return result; } + private List<String> getCrossProfileWidgetProviders(XmlPullParser parser, String tag) + throws XmlPullParserException, IOException { + int outerDepthDAM = parser.getDepth(); + int typeDAM; + ArrayList<String> result = null; + while ((typeDAM=parser.next()) != END_DOCUMENT + && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) { + if (typeDAM == END_TAG || typeDAM == TEXT) { + continue; + } + String tagDAM = parser.getName(); + if (TAG_PROVIDER.equals(tagDAM)) { + final String provider = parser.getAttributeValue(null, ATTR_VALUE); + if (result == null) { + result = new ArrayList<>(); + } + result.add(provider); + } else { + Slog.w(LOG_TAG, "Unknown tag under " + tag + ": " + tagDAM); + } + } + return result; + } + void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("uid="); pw.println(getUid()); pw.print(prefix); pw.println("policies:"); @@ -700,6 +741,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { pw.println(disableScreenCapture); pw.print(prefix); pw.print("disabledKeyguardFeatures="); pw.println(disabledKeyguardFeatures); + pw.print(prefix); pw.print("crossProfileWidgetProviders="); + pw.println(crossProfileWidgetProviders); } } @@ -757,6 +800,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addDataScheme("package"); context.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler); + + LocalServices.addService(DevicePolicyManagerInternal.class, new LocalService()); } /** @@ -1854,6 +1899,49 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public boolean addCrossProfileWidgetProvider(ComponentName admin, String packageName) { + ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (activeAdmin.crossProfileWidgetProviders == null) { + activeAdmin.crossProfileWidgetProviders = new ArrayList<>(); + } + if (activeAdmin.crossProfileWidgetProviders.add(packageName)) { + saveSettingsLocked(UserHandle.getCallingUserId()); + return true; + } + return false; + } + + @Override + public boolean removeCrossProfileWidgetProvider(ComponentName admin, String packageName) { + ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (activeAdmin.crossProfileWidgetProviders == null) { + return false; + } + if (activeAdmin.crossProfileWidgetProviders.remove(packageName)) { + saveSettingsLocked(UserHandle.getCallingUserId()); + return true; + } + return false; + } + + @Override + public List<String> getCrossProfileWidgetProviders(ComponentName admin) { + ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (activeAdmin.crossProfileWidgetProviders == null + || activeAdmin.crossProfileWidgetProviders.isEmpty()) { + return null; + } + if (Binder.getCallingUid() == Process.myUid()) { + return new ArrayList<>(activeAdmin.crossProfileWidgetProviders); + } else { + return activeAdmin.crossProfileWidgetProviders; + } + } + /** * Return a single admin's expiration date/time, or the min (soonest) for all admins. * Returns 0 if not configured. @@ -4618,4 +4706,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } + + private final class LocalService extends DevicePolicyManagerInternal { + @Override + public List<String> getCrossProfileWidgetProviders(int profileId) { + ComponentName ownerComponent = mDeviceOwner.getProfileOwnerComponent(profileId); + if (ownerComponent == null) { + return Collections.emptyList(); + } + + DevicePolicyData policy = getUserData(profileId); + ActiveAdmin admin = policy.mAdminMap.get(ownerComponent); + + if (admin.crossProfileWidgetProviders == null + || admin.crossProfileWidgetProviders.isEmpty()) { + return Collections.emptyList(); + } + + return admin.crossProfileWidgetProviders; + } + } } diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index 8a2732d..46c81b6 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -572,6 +572,13 @@ public class MockContext extends Context { /** {@hide} */ @Override + public Context createApplicationContext(ApplicationInfo application, int flags) + throws PackageManager.NameNotFoundException { + return null; + } + + /** {@hide} */ + @Override public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) throws PackageManager.NameNotFoundException { throw new UnsupportedOperationException(); @@ -595,7 +602,7 @@ public class MockContext extends Context { @Override public boolean isRestricted() { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException(); } /** @hide */ |