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

Commit d0d31e13 authored by Willie Koomson's avatar Willie Koomson Committed by Android (Google) Code Review
Browse files

Merge "Add get/setWidgetPreview API in AppWidgetManager" into main

parents 605c3bd4 3f27cd7a
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -9486,12 +9486,15 @@ package android.appwidget {
    method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForPackage(@NonNull String, @Nullable android.os.UserHandle);
    method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(@Nullable android.os.UserHandle);
    method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
    method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
    method public boolean isRequestPinAppWidgetSupported();
    method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
    method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
    method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
    method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
    method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
    method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
    method @FlaggedApi("android.appwidget.flags.generated_previews") public void setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
    method public void updateAppWidget(int[], android.widget.RemoteViews);
    method public void updateAppWidget(int, android.widget.RemoteViews);
    method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -9565,6 +9568,7 @@ package android.appwidget {
    field public int autoAdvanceViewId;
    field public android.content.ComponentName configure;
    field @IdRes public int descriptionRes;
    field @FlaggedApi("android.appwidget.flags.generated_previews") public int generatedPreviewCategories;
    field public int icon;
    field public int initialKeyguardLayout;
    field public int initialLayout;
+85 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.appwidget;
import static android.appwidget.flags.Flags.remoteAdapterConversion;

import android.annotation.BroadcastBehavior;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
@@ -30,6 +31,7 @@ import android.annotation.UiThread;
import android.annotation.UserIdInt;
import android.app.IServiceConnection;
import android.app.PendingIntent;
import android.appwidget.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -1415,6 +1417,89 @@ public class AppWidgetManager {
        }
    }

    /**
     * Set a preview for this widget. This preview will be used instead of the provider's {@link
     * AppWidgetProviderInfo#previewLayout previewLayout} or {@link
     * AppWidgetProviderInfo#previewImage previewImage} for previewing the widget in the widget
     * picker and pin app widget flow.
     *
     * @param provider The {@link ComponentName} for the {@link android.content.BroadcastReceiver
     *    BroadcastReceiver} provider for the AppWidget you intend to provide a preview for.
     * @param widgetCategories The categories that this preview should be used for. This can be a
     *    single category or combination of categories. If multiple categories are specified,
     *    then this preview will be used for each of those categories. For example, if you
     *    set a preview for WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD, the preview will
     *    be used when picking widgets for the home screen and keyguard.
     *
     *    <p>Note: You should only use the widget categories that the provider supports, as defined
     *    in {@link AppWidgetProviderInfo#widgetCategory}.
     * @param preview This preview will be used for previewing the provider when picking widgets for
     *    the selected categories.
     *
     * @see AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN
     * @see AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD
     * @see AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX
     */
    @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
    public void setWidgetPreview(@NonNull ComponentName provider,
            @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
            @NonNull RemoteViews preview) {
        try {
            mService.setWidgetPreview(provider, widgetCategories, preview);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Get the RemoteViews previews for this widget.
     *
     * @param provider The {@link ComponentName} for the {@link android.content.BroadcastReceiver
     *    BroadcastReceiver} provider for the AppWidget you intend to get a preview for.
     * @param profile The profile in which the provider resides. Passing null is equivalent
     *        to querying for only the calling user.
     * @param widgetCategory The widget category for which you want to display previews. This should
     *    be a single category. If a combination of categories is provided, this function will
     *    return a preview that matches at least one of the categories.
     *
     * @return The widget preview for the selected category, if available.
     * @see AppWidgetProviderInfo#generatedPreviewCategories
     */
    @Nullable
    @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
    public RemoteViews getWidgetPreview(@NonNull ComponentName provider,
            @Nullable UserHandle profile, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
        try {
            if (profile == null) {
                profile = mContext.getUser();
            }
            return mService.getWidgetPreview(mPackageName, provider, profile.getIdentifier(),
                    widgetCategory);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Remove this provider's preview for the specified widget categories. If the provider does not
     * have a preview for the specified widget category, this is a no-op.
     *
     * @param provider The AppWidgetProvider to remove previews for.
     * @param widgetCategories The categories of the preview to remove. For example, removing the
     *    preview for WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD will remove the
     *    previews for both categories.
     */
    @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
    public void removeWidgetPreview(@NonNull ComponentName provider,
            @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
        try {
            mService.removeWidgetPreview(provider, widgetCategories);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }


    @UiThread
    private static @NonNull Executor createUpdateExecutorIfNull() {
        if (sUpdateExecutor == null) {
+27 −0
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package android.appwidget;

import static android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS;
import static android.appwidget.flags.Flags.generatedPreviews;

import android.annotation.FlaggedApi;
import android.annotation.IdRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -358,6 +362,20 @@ public class AppWidgetProviderInfo implements Parcelable {
    /** @hide */
    public boolean isExtendedFromAppWidgetProvider;

    /**
     * Flags indicating the widget categories for which generated previews are available.
     * These correspond to the previews set by this provider with
     * {@link AppWidgetManager#setWidgetPreview}.
     *
     * @see #WIDGET_CATEGORY_HOME_SCREEN
     * @see #WIDGET_CATEGORY_KEYGUARD
     * @see #WIDGET_CATEGORY_SEARCHBOX
     * @see AppWidgetManager#getWidgetPreview
     */
    @FlaggedApi(FLAG_GENERATED_PREVIEWS)
    @SuppressLint("MutableBareField")
    public int generatedPreviewCategories;

    public AppWidgetProviderInfo() {

    }
@@ -391,6 +409,9 @@ public class AppWidgetProviderInfo implements Parcelable {
        this.widgetFeatures = in.readInt();
        this.descriptionRes = in.readInt();
        this.isExtendedFromAppWidgetProvider = in.readBoolean();
        if (generatedPreviews()) {
            generatedPreviewCategories = in.readInt();
        }
    }

    /**
@@ -515,6 +536,9 @@ public class AppWidgetProviderInfo implements Parcelable {
        out.writeInt(this.widgetFeatures);
        out.writeInt(this.descriptionRes);
        out.writeBoolean(this.isExtendedFromAppWidgetProvider);
        if (generatedPreviews()) {
            out.writeInt(this.generatedPreviewCategories);
        }
    }

    @Override
@@ -545,6 +569,9 @@ public class AppWidgetProviderInfo implements Parcelable {
        that.widgetFeatures = this.widgetFeatures;
        that.descriptionRes = this.descriptionRes;
        that.isExtendedFromAppWidgetProvider = this.isExtendedFromAppWidgetProvider;
        if (generatedPreviews()) {
            that.generatedPreviewCategories = this.generatedPreviewCategories;
        }
        return that;
    }

+6 −0
Original line number Diff line number Diff line
@@ -80,5 +80,11 @@ interface IAppWidgetService {
            in Bundle extras, in IntentSender resultIntent);
    boolean isRequestPinAppWidgetSupported();
    oneway void noteAppWidgetTapped(in String callingPackage, in int appWidgetId);
    void setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
            in RemoteViews preview);
    @nullable RemoteViews getWidgetPreview(in String callingPackage,
            in ComponentName providerComponent, in int profileId, in int widgetCategory);
    void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories);

}
+204 −2
Original line number Diff line number Diff line
@@ -362,6 +362,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
        packageFilter.addDataScheme("package");
        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                packageFilter, null, mCallbackHandler);
@@ -402,6 +403,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        boolean added = false;
        boolean changed = false;
        boolean componentsModified = false;
        int clearedUid = -1;

        final String pkgList[];
        switch (action) {
@@ -416,6 +418,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
            case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
                pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                break;
            case Intent.ACTION_PACKAGE_DATA_CLEARED:
                pkgList = null;
                clearedUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
                break;
            default: {
                Uri uri = intent.getData();
                if (uri == null) {
@@ -430,7 +436,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
            }
        }
        if (pkgList == null || pkgList.length == 0) {
        if ((pkgList == null || pkgList.length == 0) && clearedUid == -1) {
            return;
        }

@@ -461,6 +467,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                        }
                    }
                }
            } else if (clearedUid != -1) {
                componentsModified |= clearPreviewsForUidLocked(clearedUid);
            } else {
                // If the package is being updated, we'll receive a PACKAGE_ADDED
                // shortly, otherwise it is removed permanently.
@@ -486,6 +494,19 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        }
    }

    @GuardedBy("mLock")
    private boolean clearPreviewsForUidLocked(int clearedUid) {
        boolean changed = false;
        final int providerCount = mProviders.size();
        for (int i = 0; i < providerCount; i++) {
            Provider provider = mProviders.get(i);
            if (provider.id.uid == clearedUid) {
                changed |= provider.clearGeneratedPreviewsLocked();
            }
        }
        return changed;
    }

    /**
     * Reload all widgets' masked state for the given user and its associated profiles, including
     * due to user not being available and package suspension.
@@ -3904,6 +3925,124 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        }
    }

    @Override
    @Nullable
    public RemoteViews getWidgetPreview(@NonNull String callingPackage,
            @NonNull ComponentName providerComponent, int profileId,
            @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
        final int callingUserId = UserHandle.getCallingUserId();
        if (DEBUG) {
            Slog.i(TAG, "getWidgetPreview() " + callingUserId);
        }
        mSecurityPolicy.enforceCallFromPackage(callingPackage);
        ensureWidgetCategoryCombinationIsValid(widgetCategory);

        synchronized (mLock) {
            ensureGroupStateLoadedLocked(profileId);
            final int providerCount = mProviders.size();
            for (int i = 0; i < providerCount; i++) {
                Provider provider = mProviders.get(i);
                final ComponentName componentName = provider.id.componentName;
                if (provider.zombie || !providerComponent.equals(componentName)) {
                    continue;
                }

                final AppWidgetProviderInfo info = provider.getInfoLocked(mContext);
                final int providerProfileId = info.getProfile().getIdentifier();
                if (providerProfileId != profileId) {
                    continue;
                }

                // Allow access to this provider if it is from the calling package or the caller has
                // BIND_APPWIDGET permission.
                final int callingUid = Binder.getCallingUid();
                final String providerPackageName = componentName.getPackageName();
                final boolean providerIsInCallerProfile =
                        mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
                                providerPackageName, providerProfileId);
                final boolean shouldFilterAppAccess = mPackageManagerInternal.filterAppAccess(
                        providerPackageName, callingUid, providerProfileId);
                final boolean providerIsInCallerPackage =
                        mSecurityPolicy.isProviderInPackageForUid(provider, callingUid,
                                callingPackage);
                final boolean hasBindAppWidgetPermission =
                        mSecurityPolicy.hasCallerBindPermissionOrBindWhiteListedLocked(
                                callingPackage);
                if (providerIsInCallerProfile && !shouldFilterAppAccess
                        && (providerIsInCallerPackage || hasBindAppWidgetPermission)) {
                    return provider.getGeneratedPreviewLocked(widgetCategory);
                }
            }
        }
        throw new IllegalArgumentException(
                providerComponent + " is not a valid AppWidget provider");
    }

    @Override
    public void setWidgetPreview(@NonNull ComponentName providerComponent,
            @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
            @NonNull RemoteViews preview) {
        final int userId = UserHandle.getCallingUserId();
        if (DEBUG) {
            Slog.i(TAG, "setWidgetPreview() " + userId);
        }

        // Make sure callers only set previews for their own package.
        mSecurityPolicy.enforceCallFromPackage(providerComponent.getPackageName());

        ensureWidgetCategoryCombinationIsValid(widgetCategories);

        synchronized (mLock) {
            ensureGroupStateLoadedLocked(userId);

            final ProviderId providerId = new ProviderId(Binder.getCallingUid(), providerComponent);
            final Provider provider = lookupProviderLocked(providerId);
            if (provider == null) {
                throw new IllegalArgumentException(
                        providerComponent + " is not a valid AppWidget provider");
            }
            provider.setGeneratedPreviewLocked(widgetCategories, preview);
            scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
        }
    }

    @Override
    public void removeWidgetPreview(@NonNull ComponentName providerComponent,
            @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
        final int userId = UserHandle.getCallingUserId();
        if (DEBUG) {
            Slog.i(TAG, "removeWidgetPreview() " + userId);
        }

        // Make sure callers only remove previews for their own package.
        mSecurityPolicy.enforceCallFromPackage(providerComponent.getPackageName());

        ensureWidgetCategoryCombinationIsValid(widgetCategories);
        synchronized (mLock) {
            ensureGroupStateLoadedLocked(userId);

            final ProviderId providerId = new ProviderId(Binder.getCallingUid(), providerComponent);
            final Provider provider = lookupProviderLocked(providerId);
            if (provider == null) {
                throw new IllegalArgumentException(
                        providerComponent + " is not a valid AppWidget provider");
            }
            final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
            if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
        }
    }

    private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) {
        int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
                | AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
                | AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX;
        int invalid = ~validCategories;
        if ((widgetCategories & invalid) != 0) {
            throw new IllegalArgumentException(widgetCategories
                    + " is not a valid widget category combination");
        }
    }

    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;
@@ -4201,6 +4340,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        ArrayList<Widget> widgets = new ArrayList<>();
        PendingIntent broadcast;
        String infoTag;
        SparseArray<RemoteViews> generatedPreviews = new SparseArray<>(3);
        private static final int[] WIDGET_CATEGORY_FLAGS = new int[]{
                AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
                AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX,
        };

        boolean zombie; // if we're in safe mode, don't prune this just because nobody references it

@@ -4234,7 +4379,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
            return false;
        }

        @GuardedBy("AppWidgetServiceImpl.mLock")
        @GuardedBy("this.mLock")
        public AppWidgetProviderInfo getInfoLocked(Context context) {
            if (!mInfoParsed) {
                // parse
@@ -4250,6 +4395,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                    }
                    if (newInfo != null) {
                        info = newInfo;
                        updateGeneratedPreviewCategoriesLocked();
                    }
                }
                mInfoParsed = true;
@@ -4279,6 +4425,62 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
            mInfoParsed = true;
        }

        @GuardedBy("this.mLock")
        @Nullable
        public RemoteViews getGeneratedPreviewLocked(
                @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
            for (int i = 0; i < generatedPreviews.size(); i++) {
                if ((widgetCategories & generatedPreviews.keyAt(i)) != 0) {
                    return generatedPreviews.valueAt(i);
                }
            }
            return null;
        }

        @GuardedBy("this.mLock")
        public void setGeneratedPreviewLocked(
                @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
                @NonNull RemoteViews preview) {
            for (int flag : WIDGET_CATEGORY_FLAGS) {
                if ((widgetCategories & flag) != 0) {
                    generatedPreviews.put(flag, preview);
                }
            }
            updateGeneratedPreviewCategoriesLocked();
        }

        @GuardedBy("this.mLock")
        public boolean removeGeneratedPreviewLocked(int widgetCategories) {
            boolean changed = false;
            for (int flag : WIDGET_CATEGORY_FLAGS) {
                if ((widgetCategories & flag) != 0) {
                    changed |= generatedPreviews.removeReturnOld(flag) != null;
                }
            }
            if (changed) {
                updateGeneratedPreviewCategoriesLocked();
            }
            return changed;
        }

        @GuardedBy("this.mLock")
        public boolean clearGeneratedPreviewsLocked() {
            if (generatedPreviews.size() > 0) {
                generatedPreviews.clear();
                updateGeneratedPreviewCategoriesLocked();
                return true;
            }
            return false;
        }

        @GuardedBy("this.mLock")
        private void updateGeneratedPreviewCategoriesLocked() {
            info.generatedPreviewCategories = 0;
            for (int i = 0; i < generatedPreviews.size(); i++) {
                info.generatedPreviewCategories |= generatedPreviews.keyAt(i);
            }
        }

        @Override
        public String toString() {
            return "Provider{" + id + (zombie ? " Z" : "") + '}';