Loading core/api/current.txt +4 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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; core/java/android/appwidget/AppWidgetManager.java +85 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading core/java/android/appwidget/AppWidgetProviderInfo.java +27 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { } Loading Loading @@ -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(); } } /** Loading Loading @@ -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 Loading Loading @@ -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; } Loading core/java/com/android/internal/appwidget/IAppWidgetService.aidl +6 −0 Original line number Diff line number Diff line Loading @@ -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); } services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +204 −2 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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) { Loading @@ -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) { Loading @@ -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; } Loading Loading @@ -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. Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading @@ -4250,6 +4395,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } if (newInfo != null) { info = newInfo; updateGeneratedPreviewCategoriesLocked(); } } mInfoParsed = true; Loading Loading @@ -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" : "") + '}'; Loading Loading
core/api/current.txt +4 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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;
core/java/android/appwidget/AppWidgetManager.java +85 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading
core/java/android/appwidget/AppWidgetProviderInfo.java +27 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { } Loading Loading @@ -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(); } } /** Loading Loading @@ -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 Loading Loading @@ -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; } Loading
core/java/com/android/internal/appwidget/IAppWidgetService.aidl +6 −0 Original line number Diff line number Diff line Loading @@ -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); }
services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +204 −2 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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) { Loading @@ -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) { Loading @@ -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; } Loading Loading @@ -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. Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading @@ -4250,6 +4395,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } if (newInfo != null) { info = newInfo; updateGeneratedPreviewCategoriesLocked(); } } mInfoParsed = true; Loading Loading @@ -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" : "") + '}'; Loading