diff --git a/api/current.txt b/api/current.txt index b2121a85313d7..5f4115b9bda2f 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 37bb6b05c3e76..a55bbdaea56fc 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -676,6 +676,34 @@ public class AppWidgetManager { } } + /** + * Updates the info for the supplied AppWidget provider. + * + *
+ * 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. + * + *
+ * 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 a4da6b9cea183..f9bf3736422bf 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 54cf726c6b0e3..85b02206a5943 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;
@@ -1567,6 +1567,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)
@@ -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