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
This commit is contained in:
Sunny Goyal
2017-12-06 17:35:58 -08:00
parent a2e3d734fb
commit 9080cf2781
4 changed files with 131 additions and 23 deletions

View File

@@ -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";

View File

@@ -676,6 +676,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.

View File

@@ -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);

View File

@@ -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<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;