Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9080cf27 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

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
parent a2e3d734
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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";
+28 −0
Original line number Diff line number Diff line
@@ -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.
+1 −0
Original line number Diff line number Diff line
@@ -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);
+101 −23
Original line number Diff line number Diff line
@@ -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;