diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2017-12-06 17:35:58 -0800 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2018-01-08 11:39:22 -0800 |
commit | 9080cf278170ce947f4643af5b5dd80ea46d4b29 (patch) | |
tree | 6d31e58c718c4bda087fb228256674bcea92175f | |
parent | a2e3d734fb6ba6c590ac07204f73f81c943386af (diff) |
Adding support for overriding app-awidget properties at runtime
This would allow apps to customize the behaviour/appearance of a widget based
on the app state
Test: atest CtsAppWidgetTestCases:UpdateProviderInfoTest
Bug: 63931362
Change-Id: I1eef705975c2310af7311b74acc23c089fb6d1ec
4 files changed, 131 insertions, 23 deletions
diff --git a/api/current.txt b/api/current.txt index b2121a85313d..5f4115b9bda2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7362,6 +7362,7 @@ package android.appwidget { method public void updateAppWidget(int, android.widget.RemoteViews); method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews); method public void updateAppWidgetOptions(int, android.os.Bundle); + method public void updateAppWidgetProviderInfo(android.content.ComponentName, java.lang.String); field public static final java.lang.String ACTION_APPWIDGET_BIND = "android.appwidget.action.APPWIDGET_BIND"; field public static final java.lang.String ACTION_APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE"; field public static final java.lang.String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED"; diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 37bb6b05c3e7..a55bbdaea56f 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -677,6 +677,34 @@ public class AppWidgetManager { } /** + * Updates the info for the supplied AppWidget provider. + * + * <p> + * The manifest entry of the provider should contain an additional meta-data tag similar to + * {@link #META_DATA_APPWIDGET_PROVIDER} which should point to any additional definitions for + * the provider. + * + * <p> + * This is persisted across device reboots and app updates. If this meta-data key is not + * present in the manifest entry, the info reverts to default. + * + * @param provider {@link ComponentName} for the {@link + * android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget. + * @param metaDataKey key for the meta-data tag pointing to the new provider info. Use null + * to reset any previously set info. + */ + public void updateAppWidgetProviderInfo(ComponentName provider, @Nullable String metaDataKey) { + if (mService == null) { + return; + } + try { + mService.updateAppWidgetProviderInfo(provider, metaDataKey); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Notifies the specified collection view in all the specified AppWidget instances * to invalidate their data. * diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl index a4da6b9cea18..f9bf3736422b 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl @@ -56,6 +56,7 @@ interface IAppWidgetService { void partiallyUpdateAppWidgetIds(String callingPackage, in int[] appWidgetIds, in RemoteViews views); void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views); + void updateAppWidgetProviderInfo(in ComponentName provider, in String metadataKey); void notifyAppWidgetViewDataChanged(String packageName, in int[] appWidgetIds, int viewId); ParceledListSlice getInstalledProvidersForProfile(int categoryFilter, int profileId, String packageName); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 54cf726c6b0e..85b02206a594 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -120,7 +120,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -134,6 +133,7 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; @@ -1568,6 +1568,57 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } @Override + public void updateAppWidgetProviderInfo(ComponentName componentName, String metadataKey) { + final int userId = UserHandle.getCallingUserId(); + if (DEBUG) { + Slog.i(TAG, "updateAppWidgetProvider() " + userId); + } + + // 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) { + throw new IllegalArgumentException( + componentName + " is not a valid AppWidget provider"); + } + if (Objects.equals(provider.infoTag, metadataKey)) { + // No change + return; + } + + String keyToUse = metadataKey == null + ? AppWidgetManager.META_DATA_APPWIDGET_PROVIDER : metadataKey; + AppWidgetProviderInfo info = + parseAppWidgetProviderInfo(providerId, provider.info.providerInfo, keyToUse); + if (info == null) { + throw new IllegalArgumentException("Unable to parse " + keyToUse + + " meta-data to a valid AppWidget provider"); + } + + provider.info = info; + provider.infoTag = metadataKey; + + // Update all widgets for this provider + final int N = provider.widgets.size(); + for (int i = 0; i < N; i++) { + Widget widget = provider.widgets.get(i); + scheduleNotifyProviderChangedLocked(widget); + updateAppWidgetInstanceLocked(widget, widget.views, false /* isPartialUpdate */); + } + + saveGroupStateAsync(userId); + scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + } + } + + @Override public boolean isRequestPinAppWidgetSupported() { return LocalServices.getService(ShortcutServiceInternal.class) .isRequestPinItemSupported(UserHandle.getCallingUserId(), @@ -2168,7 +2219,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku ri.activityInfo.name); ProviderId providerId = new ProviderId(ri.activityInfo.applicationInfo.uid, componentName); - Provider provider = parseProviderInfoXml(providerId, ri); + Provider provider = parseProviderInfoXml(providerId, ri, null); 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 @@ -2362,6 +2413,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku out.attribute(null, "pkg", p.info.provider.getPackageName()); out.attribute(null, "cl", p.info.provider.getClassName()); out.attribute(null, "tag", Integer.toHexString(p.tag)); + if (!TextUtils.isEmpty(p.infoTag)) { + out.attribute(null, "info_tag", p.infoTag); + } out.endTag(null, "p"); } @@ -2422,17 +2476,33 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } @SuppressWarnings("deprecation") - private Provider parseProviderInfoXml(ProviderId providerId, ResolveInfo ri) { - Provider provider = null; - - ActivityInfo activityInfo = ri.activityInfo; - XmlResourceParser parser = null; - try { - parser = activityInfo.loadXmlMetaData(mContext.getPackageManager(), + private Provider parseProviderInfoXml(ProviderId providerId, ResolveInfo ri, + Provider oldProvider) { + AppWidgetProviderInfo info = null; + if (oldProvider != null && !TextUtils.isEmpty(oldProvider.infoTag)) { + info = parseAppWidgetProviderInfo(providerId, ri.activityInfo, oldProvider.infoTag); + } + if (info == null) { + info = parseAppWidgetProviderInfo(providerId, ri.activityInfo, AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); + } + if (info == null) { + return null; + } + + Provider provider = new Provider(); + provider.id = providerId; + provider.info = info; + return provider; + } + + private AppWidgetProviderInfo parseAppWidgetProviderInfo( + ProviderId providerId, ActivityInfo activityInfo, String metadataKey) { + try (XmlResourceParser parser = + activityInfo.loadXmlMetaData(mContext.getPackageManager(), metadataKey)) { if (parser == null) { - Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER - + " meta-data for " + "AppWidget provider '" + providerId + '\''); + Slog.w(TAG, "No " + metadataKey + " meta-data for AppWidget provider '" + + providerId + '\''); return null; } @@ -2452,9 +2522,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return null; } - provider = new Provider(); - provider.id = providerId; - AppWidgetProviderInfo info = provider.info = new AppWidgetProviderInfo(); + AppWidgetProviderInfo info = new AppWidgetProviderInfo(); info.provider = providerId.componentName; info.providerInfo = activityInfo; @@ -2501,7 +2569,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku className); } info.label = activityInfo.loadLabel(mContext.getPackageManager()).toString(); - info.icon = ri.getIconResource(); + info.icon = activityInfo.getIconResource(); info.previewImage = sa.getResourceId( com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); info.autoAdvanceViewId = sa.getResourceId( @@ -2516,6 +2584,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku com.android.internal.R.styleable.AppWidgetProviderInfo_widgetFeatures, 0); sa.recycle(); + return info; } 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 @@ -2523,12 +2592,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Slog.w(TAG, "XML parsing failed for AppWidget provider " + providerId.componentName + " for user " + providerId.uid, e); return null; - } finally { - if (parser != null) { - parser.close(); - } } - return provider; } private int getUidForPackage(String packageName, int userId) { @@ -2891,7 +2955,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (provider.getUserId() != userId) { continue; } - if (provider.widgets.size() > 0) { + if (provider.shouldBePersisted()) { serializeProvider(out, provider); } } @@ -3000,6 +3064,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku final int providerTag = !TextUtils.isEmpty(tagAttribute) ? Integer.parseInt(tagAttribute, 16) : legacyProviderIndex; provider.tag = providerTag; + + provider.infoTag = parser.getAttributeValue(null, "info_tag"); + if (!TextUtils.isEmpty(provider.infoTag) && !mSafeMode) { + AppWidgetProviderInfo info = parseAppWidgetProviderInfo( + providerId, providerInfo, provider.infoTag); + if (info != null) { + provider.info = info; + } + } } else if ("h".equals(tag)) { legacyHostIndex++; Host host = new Host(); @@ -3254,7 +3327,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku providersUpdated = true; } } else { - Provider parsed = parseProviderInfoXml(providerId, ri); + Provider parsed = parseProviderInfoXml(providerId, ri, provider); if (parsed != null) { keep.add(providerId); // Use the new AppWidgetProviderInfo. @@ -3725,6 +3798,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info; ArrayList<Widget> widgets = new ArrayList<>(); PendingIntent broadcast; + String infoTag; boolean zombie; // if we're in safe mode, don't prune this just because nobody references it boolean maskedByLockedProfile; @@ -3784,6 +3858,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku public boolean isMaskedLocked() { return maskedByQuietProfile || maskedByLockedProfile || maskedBySuspendedPackage; } + + public boolean shouldBePersisted() { + return !widgets.isEmpty() || !TextUtils.isEmpty(infoTag); + } } private static final class ProviderId { @@ -4114,7 +4192,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku for (int i = 0; i < N; i++) { Provider provider = mProviders.get(i); - if (!provider.widgets.isEmpty() + if (provider.shouldBePersisted() && (provider.isInPackageForUser(backedupPackage, userId) || provider.hostedByPackageForUser(backedupPackage, userId))) { provider.tag = index; |