diff options
| author | 2024-10-28 19:32:21 +0000 | |
|---|---|---|
| committer | 2024-10-28 19:32:21 +0000 | |
| commit | 0752f7dd0c602e7ff6288e74aad2633c0e733639 (patch) | |
| tree | 4547867c14ad332d658cbf468faba04087d3bc62 | |
| parent | 534cbb1d2b730843ed2b7e7454f6314495f486d3 (diff) | |
| parent | 63a8e1678db1e0f07ee06e781b6e8a8d98bbfc28 (diff) | |
Merge "Save generated previews in AppWidgetService" into main
| -rw-r--r-- | core/java/android/appwidget/flags.aconfig | 2 | ||||
| -rw-r--r-- | core/proto/android/service/appwidget.proto | 13 | ||||
| -rw-r--r-- | services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java | 440 |
3 files changed, 439 insertions, 16 deletions
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig index 3839b5fa2599..e5c94fccfe28 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -55,7 +55,7 @@ flag { name: "remote_views_proto" namespace: "app_widgets" description: "Enable support for persisting RemoteViews previews to Protobuf" - bug: "306546610" + bug: "364420494" } flag { diff --git a/core/proto/android/service/appwidget.proto b/core/proto/android/service/appwidget.proto index 97350ef90eec..fb907196bfc7 100644 --- a/core/proto/android/service/appwidget.proto +++ b/core/proto/android/service/appwidget.proto @@ -20,6 +20,8 @@ package android.service.appwidget; option java_multiple_files = true; option java_outer_classname = "AppWidgetServiceProto"; +import "frameworks/base/core/proto/android/widget/remoteviews.proto"; + // represents the object holding the dump info of the app widget service message AppWidgetServiceDumpProto { repeated WidgetProto widgets = 1; // the array of bound widgets @@ -38,3 +40,14 @@ message WidgetProto { optional int32 maxHeight = 9; optional bool restoreCompleted = 10; } + +// represents a set of widget previews for a particular provider +message GeneratedPreviewsProto { + repeated Preview previews = 1; + + // represents a particular RemoteViews preview, which may be set for multiple categories + message Preview { + repeated int32 widget_categories = 1; + optional android.widget.RemoteViewsProto views = 2; + } +}
\ No newline at end of file diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index f6ac706c4985..35998d9a1fd4 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -17,6 +17,7 @@ package com.android.server.appwidget; import static android.appwidget.flags.Flags.remoteAdapterConversion; +import static android.appwidget.flags.Flags.remoteViewsProto; import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath; import static android.appwidget.flags.Flags.securityPolicyInteractAcrossUsers; import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot; @@ -31,6 +32,7 @@ import static com.android.server.appwidget.AppWidgetXmlUtil.serializeWidgetSizes import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.PermissionName; @@ -104,6 +106,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; import android.service.appwidget.AppWidgetServiceDumpProto; +import android.service.appwidget.GeneratedPreviewsProto; import android.service.appwidget.WidgetProto; import android.text.TextUtils; import android.util.ArrayMap; @@ -122,7 +125,9 @@ import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TypedValue; import android.util.Xml; +import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import android.util.proto.ProtoUtils; import android.view.Display; import android.view.View; import android.widget.RemoteViews; @@ -134,6 +139,7 @@ import com.android.internal.app.UnlaunchableAppActivity; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; +import com.android.internal.infra.AndroidFuture; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; @@ -221,6 +227,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // XML attribute for widget ids that are pending deletion. // See {@link Provider#pendingDeletedWidgetIds}. private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids"; + // Name of service directory in /data/system_ce/<user>/ + private static final String APPWIDGET_CE_DATA_DIRNAME = "appwidget"; + // Name of previews directory in /data/system_ce/<user>/appwidget/ + private static final String WIDGET_PREVIEWS_DIRNAME = "previews"; // Hard limit of number of hosts an app can create, note that the app that hosts the widgets // can have multiple instances of {@link AppWidgetHost}, typically in respect to different @@ -316,6 +326,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Handler to the background thread that saves states to disk. private Handler mSaveStateHandler; + // Handler to the background thread that saves generated previews to disk. All operations that + // modify saved previews must be run on this Handler. + private Handler mSavePreviewsHandler; // Handler to the foreground thread that handles broadcasts related to user // and package events, as well as various internal events within // AppWidgetService. @@ -359,6 +372,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } else { mSaveStateHandler = BackgroundThread.getHandler(); } + mSavePreviewsHandler = new Handler(BackgroundThread.get().getLooper()); final ServiceThread serviceThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */); serviceThread.start(); @@ -378,7 +392,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS, DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS); mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval, - generatedPreviewMaxCallsPerInterval, generatedPreviewsMaxProviders); + generatedPreviewMaxCallsPerInterval, + // Set a limit on the number of providers if storing them in memory. + remoteViewsProto() ? Integer.MAX_VALUE : generatedPreviewsMaxProviders); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange); @@ -644,7 +660,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku for (int i = 0; i < providerCount; i++) { Provider provider = mProviders.get(i); if (provider.id.uid == clearedUid) { - changed |= provider.clearGeneratedPreviewsLocked(); + if (remoteViewsProto()) { + changed |= clearGeneratedPreviewsAsync(provider); + } else { + changed |= provider.clearGeneratedPreviewsLocked(); + } + if (DEBUG) { + Slog.e(TAG, "clearPreviewsForUidLocked " + provider + " changed " + changed); + } } } return changed; @@ -3246,6 +3269,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku deleteWidgetsLocked(provider, UserHandle.USER_ALL); mProviders.remove(provider); mGeneratedPreviewsApiCounter.remove(provider.id); + if (remoteViewsProto()) { + clearGeneratedPreviewsAsync(provider); + } // no need to send the DISABLE broadcast, since the receiver is gone anyway cancelBroadcastsLocked(provider); @@ -3824,6 +3850,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } catch (IOException e) { Slog.w(TAG, "Failed to read state: " + e); } + + if (remoteViewsProto()) { + try { + loadGeneratedPreviewCategoriesLocked(profileId); + } catch (IOException e) { + Slog.w(TAG, "Failed to read preview categories: " + e); + } + } } if (version >= 0) { @@ -4593,6 +4627,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku keep.add(providerId); // Use the new AppWidgetProviderInfo. provider.setPartialInfoLocked(info); + // Clear old previews + if (remoteViewsProto()) { + clearGeneratedPreviewsAsync(provider); + } else { + provider.clearGeneratedPreviewsLocked(); + } // If it's enabled final int M = provider.widgets.size(); if (M > 0) { @@ -4884,6 +4924,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku mSecurityPolicy.enforceCallFromPackage(callingPackage); ensureWidgetCategoryCombinationIsValid(widgetCategory); + AndroidFuture<RemoteViews> result = null; synchronized (mLock) { ensureGroupStateLoadedLocked(profileId); final int providerCount = mProviders.size(); @@ -4917,10 +4958,23 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku callingPackage); if (providerIsInCallerProfile && !shouldFilterAppAccess && (providerIsInCallerPackage || hasBindAppWidgetPermission)) { - return provider.getGeneratedPreviewLocked(widgetCategory); + if (remoteViewsProto()) { + result = getGeneratedPreviewsAsync(provider, widgetCategory); + } else { + return provider.getGeneratedPreviewLocked(widgetCategory); + } } } } + + if (result != null) { + try { + return result.get(); + } catch (Exception e) { + Slog.e(TAG, "Failed to get generated previews Future result", e); + return null; + } + } // Either the provider does not exist or the caller does not have permission to access its // previews. return null; @@ -4950,8 +5004,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku providerComponent + " is not a valid AppWidget provider"); } if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) { - provider.setGeneratedPreviewLocked(widgetCategories, preview); - scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + if (remoteViewsProto()) { + setGeneratedPreviewsAsync(provider, widgetCategories, preview); + } else { + provider.setGeneratedPreviewLocked(widgetCategories, preview); + scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + } return true; } return false; @@ -4979,11 +5037,361 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku throw new IllegalArgumentException( providerComponent + " is not a valid AppWidget provider"); } - final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories); - if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + + if (remoteViewsProto()) { + removeGeneratedPreviewsAsync(provider, widgetCategories); + } else { + final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories); + if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + } + } + } + + /** + * Return previews for the specified provider from a background thread. The result of the future + * is nullable. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private AndroidFuture<RemoteViews> getGeneratedPreviewsAsync( + @NonNull Provider provider, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) { + AndroidFuture<RemoteViews> result = new AndroidFuture<>(); + mSavePreviewsHandler.post(() -> { + SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider); + for (int i = 0; i < previews.size(); i++) { + if ((widgetCategory & previews.keyAt(i)) != 0) { + result.complete(previews.valueAt(i)); + return; + } + } + result.complete(null); + }); + return result; + } + + /** + * Set previews for the specified provider on a background thread. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void setGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories, + @NonNull RemoteViews preview) { + mSavePreviewsHandler.post(() -> { + SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider); + for (int flag : Provider.WIDGET_CATEGORY_FLAGS) { + if ((widgetCategories & flag) != 0) { + previews.put(flag, preview); + } + } + saveGeneratedPreviews(provider, previews, /* notify= */ true); + }); + } + + /** + * Remove previews for the specified provider on a background thread. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void removeGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories) { + mSavePreviewsHandler.post(() -> { + SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider); + boolean changed = false; + for (int flag : Provider.WIDGET_CATEGORY_FLAGS) { + if ((widgetCategories & flag) != 0) { + changed |= previews.removeReturnOld(flag) != null; + } + } + if (changed) { + saveGeneratedPreviews(provider, previews, /* notify= */ true); + } + }); + } + + /** + * Clear previews for the specified provider on a background thread. Returns true if changed + * (i.e. there are previews to clear). If returns true, the caller should schedule a providers + * changed notification. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private boolean clearGeneratedPreviewsAsync(@NonNull Provider provider) { + mSavePreviewsHandler.post(() -> { + saveGeneratedPreviews(provider, /* previews= */ null, /* notify= */ false); + }); + return provider.info.generatedPreviewCategories != 0; + } + + private void checkSavePreviewsThread() { + if (DEBUG && !mSavePreviewsHandler.getLooper().isCurrentThread()) { + throw new IllegalStateException("Only modify previews on the background thread"); } } + /** + * Load previews from file for the given provider. If there are no previews, returns an empty + * SparseArray. Else, returns a SparseArray of the previews mapped by widget category. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private SparseArray<RemoteViews> loadGeneratedPreviews(@NonNull Provider provider) { + checkSavePreviewsThread(); + try { + AtomicFile previewsFile = getWidgetPreviewsFile(provider); + if (!previewsFile.exists()) { + return new SparseArray<>(); + } + ProtoInputStream input = new ProtoInputStream(previewsFile.readFully()); + SparseArray<RemoteViews> entries = readGeneratedPreviewsFromProto(input); + SparseArray<RemoteViews> singleCategoryKeyedEntries = new SparseArray<>(); + for (int i = 0; i < entries.size(); i++) { + int widgetCategories = entries.keyAt(i); + RemoteViews preview = entries.valueAt(i); + for (int flag : Provider.WIDGET_CATEGORY_FLAGS) { + if ((widgetCategories & flag) != 0) { + singleCategoryKeyedEntries.put(flag, preview); + } + } + } + return singleCategoryKeyedEntries; + } catch (IOException e) { + Slog.e(TAG, "Failed to load generated previews for " + provider, e); + return new SparseArray<>(); + } + } + + /** + * This is called when loading profile/group state to populate + * AppWidgetProviderInfo.generatedPreviewCategories based on what previews are saved. + * + * This is the only time previews are read while not on mSavePreviewsHandler. It happens once + * per profile during initialization, before any calls to get/set/removeWidgetPreviewAsync + * happen for that profile. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @GuardedBy("mLock") + private void loadGeneratedPreviewCategoriesLocked(int profileId) throws IOException { + for (Provider provider : mProviders) { + if (provider.id.getProfile().getIdentifier() != profileId) { + continue; + } + AtomicFile previewsFile = getWidgetPreviewsFile(provider); + if (!previewsFile.exists()) { + continue; + } + ProtoInputStream input = new ProtoInputStream(previewsFile.readFully()); + provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto( + input); + if (DEBUG) { + Slog.i(TAG, TextUtils.formatSimple( + "loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId, + provider, provider.info.generatedPreviewCategories)); + } + } + } + + /** + * Save the given previews into storage. + * + * @param provider Provider for which to save previews + * @param previews Previews to save. If null or empty, clears any saved previews for this + * provider. + * @param notify If true, then this function will notify hosts of updated provider info. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void saveGeneratedPreviews(@NonNull Provider provider, + @Nullable SparseArray<RemoteViews> previews, boolean notify) { + checkSavePreviewsThread(); + AtomicFile file = null; + FileOutputStream stream = null; + try { + file = getWidgetPreviewsFile(provider); + if (previews == null || previews.size() == 0) { + if (file.exists()) { + if (DEBUG) { + Slog.i(TAG, "Deleting widget preview file " + file); + } + file.delete(); + } + } else { + if (DEBUG) { + Slog.i(TAG, "Writing widget preview file " + file); + } + ProtoOutputStream out = new ProtoOutputStream(); + writePreviewsToProto(out, previews); + stream = file.startWrite(); + stream.write(out.getBytes()); + file.finishWrite(stream); + } + + synchronized (mLock) { + provider.updateGeneratedPreviewCategoriesLocked(previews); + if (notify) { + scheduleNotifyGroupHostsForProvidersChangedLocked(provider.getUserId()); + } + } + } catch (IOException e) { + if (file != null && stream != null) { + file.failWrite(stream); + } + Slog.w(TAG, "Failed to save widget previews for provider " + provider.id.componentName); + } + } + + + /** + * Write the given previews as a GeneratedPreviewsProto to the output stream. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + private void writePreviewsToProto(@NonNull ProtoOutputStream out, + @NonNull SparseArray<RemoteViews> generatedPreviews) { + // Collect RemoteViews mapped by hashCode in order to avoid writing duplicates. + SparseArray<Pair<Integer, RemoteViews>> previewsToWrite = new SparseArray<>(); + for (int i = 0; i < generatedPreviews.size(); i++) { + int widgetCategory = generatedPreviews.keyAt(i); + RemoteViews views = generatedPreviews.valueAt(i); + if (!previewsToWrite.contains(views.hashCode())) { + previewsToWrite.put(views.hashCode(), new Pair<>(widgetCategory, views)); + } else { + Pair<Integer, RemoteViews> entry = previewsToWrite.get(views.hashCode()); + previewsToWrite.put(views.hashCode(), + Pair.create(entry.first | widgetCategory, views)); + } + } + + for (int i = 0; i < previewsToWrite.size(); i++) { + final long token = out.start(GeneratedPreviewsProto.PREVIEWS); + Pair<Integer, RemoteViews> entry = previewsToWrite.valueAt(i); + out.write(GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES, entry.first); + final long viewsToken = out.start(GeneratedPreviewsProto.Preview.VIEWS); + entry.second.writePreviewToProto(mContext, out); + out.end(viewsToken); + out.end(token); + } + } + + /** + * Read a GeneratedPreviewsProto message from the input stream. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private SparseArray<RemoteViews> readGeneratedPreviewsFromProto(@NonNull ProtoInputStream input) + throws IOException { + SparseArray<RemoteViews> entries = new SparseArray<>(); + while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (input.getFieldNumber()) { + case (int) GeneratedPreviewsProto.PREVIEWS: + final long token = input.start(GeneratedPreviewsProto.PREVIEWS); + Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input, + /* skipViews= */ false); + entries.put(entry.first, entry.second); + input.end(token); + break; + default: + Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! " + + ProtoUtils.currentFieldToString(input)); + } + } + return entries; + } + + /** + * Read the widget categories from GeneratedPreviewsProto and return an int representing the + * combined widget categories of all the previews. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @AppWidgetProviderInfo.CategoryFlags + private int readGeneratedPreviewCategoriesFromProto(@NonNull ProtoInputStream input) + throws IOException { + int widgetCategories = 0; + while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (input.getFieldNumber()) { + case (int) GeneratedPreviewsProto.PREVIEWS: + final long token = input.start(GeneratedPreviewsProto.PREVIEWS); + Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input, + /* skipViews= */ true); + widgetCategories |= entry.first; + input.end(token); + break; + default: + Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! " + + ProtoUtils.currentFieldToString(input)); + } + } + return widgetCategories; + } + + /** + * Read a single GeneratedPreviewsProto.Preview message from the input stream, and returns a + * pair of widget category and corresponding RemoteViews. If skipViews is true, this function + * will only read widget categories and the returned RemoteViews will be null. + */ + @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO) + @NonNull + private Pair<Integer, RemoteViews> readSinglePreviewFromProto(@NonNull ProtoInputStream input, + boolean skipViews) throws IOException { + int widgetCategories = 0; + RemoteViews views = null; + while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (input.getFieldNumber()) { + case (int) GeneratedPreviewsProto.Preview.VIEWS: + if (skipViews) { + // ProtoInputStream will skip over the nested message when nextField() is + // called. + continue; + } + final long token = input.start(GeneratedPreviewsProto.Preview.VIEWS); + try { + views = RemoteViews.createPreviewFromProto(mContext, input); + } catch (Exception e) { + Slog.e(TAG, "Unable to deserialize RemoteViews", e); + } + input.end(token); + break; + case (int) GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES: + widgetCategories = input.readInt( + GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES); + break; + default: + Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! " + + ProtoUtils.currentFieldToString(input)); + } + } + return Pair.create(widgetCategories, views); + } + + /** + * Returns the file in which all generated previews for this provider are stored. This will be + * a path of the form: + * {@literal /data/system_ce/<userId>/appwidget/previews/<package>-<class>-<uid>.binpb} + * + * This function will not create the file if it does not already exist. + */ + @NonNull + private static AtomicFile getWidgetPreviewsFile(@NonNull Provider provider) throws IOException { + int userId = provider.getUserId(); + File previewsDirectory = getWidgetPreviewsDirectory(userId); + File providerPreviews = Environment.buildPath(previewsDirectory, + TextUtils.formatSimple("%s-%s-%d.binpb", provider.id.componentName.getPackageName(), + provider.id.componentName.getClassName(), provider.id.uid)); + return new AtomicFile(providerPreviews); + } + + /** + * Returns the widget previews directory for the given user, creating it if it does not exist. + * This will be a path of the form: + * {@literal /data/system_ce/<userId>/appwidget/previews} + */ + @NonNull + private static File getWidgetPreviewsDirectory(int userId) throws IOException { + File dataSystemCeDirectory = Environment.getDataSystemCeDirectory(userId); + File previewsDirectory = Environment.buildPath(dataSystemCeDirectory, + APPWIDGET_CE_DATA_DIRNAME, WIDGET_PREVIEWS_DIRNAME); + if (!previewsDirectory.exists()) { + if (!previewsDirectory.mkdirs()) { + throw new IOException("Unable to create widget preview directory " + + previewsDirectory.getPath()); + } + } + return previewsDirectory; + } + private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) { int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN | AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD @@ -5415,11 +5823,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); } if (newInfo != null) { + newInfo.generatedPreviewCategories = info.generatedPreviewCategories; info = newInfo; if (DEBUG) { Objects.requireNonNull(info); } - updateGeneratedPreviewCategoriesLocked(); } } mInfoParsed = true; @@ -5476,7 +5884,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku generatedPreviews.put(flag, preview); } } - updateGeneratedPreviewCategoriesLocked(); + updateGeneratedPreviewCategoriesLocked(generatedPreviews); } @GuardedBy("this.mLock") @@ -5488,7 +5896,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } if (changed) { - updateGeneratedPreviewCategoriesLocked(); + updateGeneratedPreviewCategoriesLocked(generatedPreviews); } return changed; } @@ -5497,17 +5905,19 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku public boolean clearGeneratedPreviewsLocked() { if (generatedPreviews.size() > 0) { generatedPreviews.clear(); - updateGeneratedPreviewCategoriesLocked(); + updateGeneratedPreviewCategoriesLocked(generatedPreviews); return true; } return false; } - @GuardedBy("this.mLock") - private void updateGeneratedPreviewCategoriesLocked() { + private void updateGeneratedPreviewCategoriesLocked( + @Nullable SparseArray<RemoteViews> previews) { info.generatedPreviewCategories = 0; - for (int i = 0; i < generatedPreviews.size(); i++) { - info.generatedPreviewCategories |= generatedPreviews.keyAt(i); + if (previews != null) { + for (int i = 0; i < previews.size(); i++) { + info.generatedPreviewCategories |= previews.keyAt(i); + } } } |