summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSvetoslav <svetoslavganov@google.com>2014-07-16 15:12:03 -0700
committerTorne (Richard Coles) <torne@google.com>2014-08-06 11:01:36 +0100
commit39999cf0263d0568337a87d0e586dcafd9e22ba1 (patch)
tree1e38f5f5a2d899db203ea4375d11bbb4369fad36
parent8a0ee34c7047b0b3c0869b0c173a210b40fd3050 (diff)
downloadframeworks_base-39999cf0263d0568337a87d0e586dcafd9e22ba1.zip
frameworks_base-39999cf0263d0568337a87d0e586dcafd9e22ba1.tar.gz
frameworks_base-39999cf0263d0568337a87d0e586dcafd9e22ba1.tar.bz2
Allow adding widgets from user profiles.
The goal of this change is to enable support for appwidget from user profiles to the user main profile. A user profile is a user which is associated as a child of the main user profile. For example, a user may have a personal (parent) and corporate (child) profile. The device policy should be able to control whether adding a widget from a child profile and given packages is allowed. This change assumes that all packages from managed profiles are white listed. Another change will add the device policy changes. (cherrypicked 8cd27c3c to work around Gerrit issue) Change-Id: I267260b55d74c48b112a29979a9f59eef7a8194e
-rw-r--r--api/current.txt18
-rw-r--r--cmds/appwidget/src/com/android/commands/appwidget/AppWidget.java2
-rw-r--r--core/java/android/app/ContextImpl.java28
-rw-r--r--core/java/android/app/Notification.java22
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java84
-rw-r--r--core/java/android/app/admin/DevicePolicyManagerInternal.java38
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl3
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java131
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java27
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java413
-rw-r--r--core/java/android/appwidget/AppWidgetProviderInfo.java140
-rw-r--r--core/java/android/content/Context.java11
-rw-r--r--core/java/android/content/ContextWrapper.java6
-rw-r--r--core/java/android/service/notification/StatusBarNotification.java2
-rw-r--r--core/java/android/widget/AnalogClock.java10
-rw-r--r--core/java/android/widget/RemoteViews.java167
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java42
-rw-r--r--core/java/android/widget/ViewFlipper.java14
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetHost.aidl9
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetService.aidl53
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetService.java401
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java5003
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java114
-rw-r--r--test-runner/src/android/test/mock/MockContext.java9
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>&lt;receiver&gt;</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>&lt;receiver&gt;</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>&lt;receiver&gt;</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>&lt;receiver&gt;</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>&lt;receiver&gt;</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 */