Loading services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +163 −22 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.appwidget; import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath; import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot; import static android.content.Context.KEYGUARD_SERVICE; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; Loading Loading @@ -166,6 +167,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.LongSupplier; class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider, Loading Loading @@ -457,7 +460,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku break; case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: added = true; // Follow through // fall through case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE: pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); break; Loading Loading @@ -3571,6 +3574,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } out.endTag(null, "gs"); if (supportResumeRestoreAfterReboot() && mBackupRestoreController.requiresPersistenceLocked()) { AppWidgetXmlUtil.writeBackupRestoreControllerState( out, mBackupRestoreController.getStateLocked(userId)); } out.endDocument(); return true; } catch (IOException e) { Loading Loading @@ -3717,6 +3727,32 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku LoadedWidgetState loadedWidgets = new LoadedWidgetState(widget, hostTag, providerTag); outLoadedWidgets.add(loadedWidgets); } else if (supportResumeRestoreAfterReboot() && AppWidgetXmlUtil.TAG_BACKUP_RESTORE_CONTROLLER_STATE.equals(tag)) { final BackupRestoreController.State s = AppWidgetXmlUtil.readBackupRestoreControllerState(parser); if (s == null) { continue; } final Set<String> prunedAppsInFile = s.getPrunedApps(); if (prunedAppsInFile != null) { final Set<String> prunedAppsInMemory = mBackupRestoreController .mPrunedAppsPerUser.get(userId); if (prunedAppsInMemory == null) { mBackupRestoreController.mPrunedAppsPerUser.put( userId, prunedAppsInFile); } else { prunedAppsInMemory.addAll(prunedAppsInFile); } } loadUpdateRecords(s.getUpdatesByProvider(), this::findProviderByTag, mBackupRestoreController.mUpdatesByProvider::get, mBackupRestoreController.mUpdatesByProvider::put); loadUpdateRecords(s.getUpdatesByHost(), this::findHostByTag, mBackupRestoreController.mUpdatesByHost::get, mBackupRestoreController.mUpdatesByHost::put); } } } while (type != XmlPullParser.END_DOCUMENT); Loading @@ -3732,6 +3768,36 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return version; } private <T> void loadUpdateRecords( @Nullable final SparseArray< List<BackupRestoreController.RestoreUpdateRecord>> updatesOnFile, @NonNull final Function<Integer, T> findKeyByTagCb, @NonNull final Function<T, List< BackupRestoreController.RestoreUpdateRecord>> findRecordsCb, @NonNull final BiConsumer<T, List< BackupRestoreController.RestoreUpdateRecord>> newRecordsCb) { if (updatesOnFile == null) { return; } for (int i = 0; i < updatesOnFile.size(); i++) { final int tag = updatesOnFile.keyAt(i); final List< BackupRestoreController.RestoreUpdateRecord > recordsOnFile = updatesOnFile.get(tag); if (recordsOnFile == null || recordsOnFile.isEmpty()) { continue; } final T key = findKeyByTagCb.apply(tag); final List<BackupRestoreController.RestoreUpdateRecord> recordsInMemory = findRecordsCb.apply(key); if (recordsInMemory != null) { recordsInMemory.addAll(recordsOnFile); } else { newRecordsCb.accept(key, recordsOnFile); } } } private void performUpgradeLocked(int fromVersion) { if (fromVersion < CURRENT_VERSION) { Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to " Loading Loading @@ -4674,7 +4740,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } private static final class Provider { static final class Provider { ProviderId id; AppWidgetProviderInfo info; Loading Loading @@ -4931,7 +4997,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } private static final class Host { static final class Host { HostId id; ArrayList<Widget> widgets = new ArrayList<>(); IAppWidgetHost callbacks; Loading Loading @@ -5250,10 +5316,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku /** * This class encapsulates the backup and restore logic for a user group state. */ private final class BackupRestoreController { final class BackupRestoreController { private static final String TAG = "BackupRestoreController"; private static final boolean DEBUG = true; private static final boolean DEBUG = AppWidgetServiceImpl.DEBUG; // Version of backed-up widget state. private static final int WIDGET_STATE_VERSION = 2; Loading @@ -5262,16 +5328,31 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // a given package. Keep track of what we've done so far here; the list is // cleared at the start of every system restore pass, but preserved through // any install-time restore operations. @GuardedBy("AppWidgetServiceImpl.this.mLock") private final SparseArray<Set<String>> mPrunedAppsPerUser = new SparseArray<>(); private final HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider = new HashMap<>(); private final HashMap<Host, ArrayList<RestoreUpdateRecord>> mUpdatesByHost = new HashMap<>(); @GuardedBy("AppWidgetServiceImpl.this.mLock") final Map<Provider, List<RestoreUpdateRecord>> mUpdatesByProvider = new ArrayMap<>(); @GuardedBy("mLock") @GuardedBy("AppWidgetServiceImpl.this.mLock") private final Map<Host, List<RestoreUpdateRecord>> mUpdatesByHost = new ArrayMap<>(); @GuardedBy("AppWidgetServiceImpl.this.mLock") private boolean mHasSystemRestoreFinished; @GuardedBy("AppWidgetServiceImpl.this.mLock") public boolean requiresPersistenceLocked() { if (mHasSystemRestoreFinished) { // No need to persist intermediate states if system restore is already finished. return false; } // If either of the internal states is non-empty, then we need to persist that return !(mPrunedAppsPerUser.size() == 0 && mUpdatesByProvider.isEmpty() && mUpdatesByHost.isEmpty()); } public List<String> getWidgetParticipants(int userId) { if (DEBUG) { Slog.i(TAG, "Getting widget participants for user: " + userId); Loading Loading @@ -5436,7 +5517,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // If there's no live entry for this provider, add an inactive one // so that widget IDs referring to them can be properly allocated // Backup and resotre only for the parent profile. // Backup and restore only for the parent profile. ComponentName componentName = new ComponentName(pkg, cl); Provider p = findProviderLocked(componentName, userId); Loading Loading @@ -5579,9 +5660,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku final UserHandle userHandle = new UserHandle(userId); // Build the providers' broadcasts and send them off Set<Map.Entry<Provider, ArrayList<RestoreUpdateRecord>>> providerEntries Set<Map.Entry<Provider, List<RestoreUpdateRecord>>> providerEntries = mUpdatesByProvider.entrySet(); for (Map.Entry<Provider, ArrayList<RestoreUpdateRecord>> e : providerEntries) { for (Map.Entry<Provider, List<RestoreUpdateRecord>> e : providerEntries) { // For each provider there's a list of affected IDs Provider provider = e.getKey(); if (provider.zombie) { Loading @@ -5589,7 +5670,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // We'll be called again when the provider is installed. continue; } ArrayList<RestoreUpdateRecord> updates = e.getValue(); List<RestoreUpdateRecord> updates = e.getValue(); final int pending = countPendingUpdates(updates); if (DEBUG) { Slog.i(TAG, "Provider " + provider + " pending: " + pending); Loading Loading @@ -5618,12 +5699,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } // same thing per host Set<Map.Entry<Host, ArrayList<RestoreUpdateRecord>>> hostEntries Set<Map.Entry<Host, List<RestoreUpdateRecord>>> hostEntries = mUpdatesByHost.entrySet(); for (Map.Entry<Host, ArrayList<RestoreUpdateRecord>> e : hostEntries) { for (Map.Entry<Host, List<RestoreUpdateRecord>> e : hostEntries) { Host host = e.getKey(); if (host.id.uid != UNKNOWN_UID) { ArrayList<RestoreUpdateRecord> updates = e.getValue(); List<RestoreUpdateRecord> updates = e.getValue(); final int pending = countPendingUpdates(updates); if (DEBUG) { Slog.i(TAG, "Host " + host + " pending: " + pending); Loading Loading @@ -5714,8 +5795,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return false; } @GuardedBy("mLock") private void stashProviderRestoreUpdateLocked(Provider provider, int oldId, int newId) { ArrayList<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider); List<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider); if (r == null) { r = new ArrayList<>(); mUpdatesByProvider.put(provider, r); Loading @@ -5732,7 +5814,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku r.add(new RestoreUpdateRecord(oldId, newId)); } private boolean alreadyStashed(ArrayList<RestoreUpdateRecord> stash, private boolean alreadyStashed(List<RestoreUpdateRecord> stash, final int oldId, final int newId) { final int N = stash.size(); for (int i = 0; i < N; i++) { Loading @@ -5744,8 +5826,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return false; } @GuardedBy("mLock") private void stashHostRestoreUpdateLocked(Host host, int oldId, int newId) { ArrayList<RestoreUpdateRecord> r = mUpdatesByHost.get(host); List<RestoreUpdateRecord> r = mUpdatesByHost.get(host); if (r == null) { r = new ArrayList<>(); mUpdatesByHost.put(host, r); Loading Loading @@ -5835,7 +5918,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku || widget.provider.getUserId() == userId); } private int countPendingUpdates(ArrayList<RestoreUpdateRecord> updates) { private int countPendingUpdates(List<RestoreUpdateRecord> updates) { int pending = 0; final int N = updates.size(); for (int i = 0; i < N; i++) { Loading @@ -5847,9 +5930,28 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return pending; } @GuardedBy("mLock") @NonNull private State getStateLocked(final int userId) { final Set<String> prunedApps = mPrunedAppsPerUser.get(userId); final SparseArray<List<RestoreUpdateRecord>> updatesByProvider = new SparseArray<>(); final SparseArray<List<RestoreUpdateRecord>> updatesByHost = new SparseArray<>(); mUpdatesByProvider.forEach((p, updates) -> { if (p.getUserId() == userId) { updatesByProvider.put(p.tag, new ArrayList<>(updates)); } }); mUpdatesByHost.forEach((h, updates) -> { if (h.getUserId() == userId) { updatesByHost.put(h.tag, new ArrayList<>(updates)); } }); return new State(prunedApps, updatesByProvider, updatesByHost); } // Accumulate a list of updates that affect the given provider for a final // coalesced notification broadcast once restore is over. private class RestoreUpdateRecord { static class RestoreUpdateRecord { public int oldId; public int newId; public boolean notified; Loading @@ -5860,6 +5962,45 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku notified = false; } } static final class State { // We need to make sure to wipe the pre-restore widget state only once for // a given package. Keep track of what we've done so far here; the list is // cleared at the start of every system restore pass, but preserved through // any install-time restore operations. @Nullable private final Set<String> mPrunedApps; @Nullable private final SparseArray<List<RestoreUpdateRecord>> mUpdatesByProvider; @Nullable private final SparseArray<List<RestoreUpdateRecord>> mUpdatesByHost; State( @Nullable final Set<String> prunedApps, @Nullable final SparseArray<List<RestoreUpdateRecord>> updatesByProvider, @Nullable final SparseArray<List<RestoreUpdateRecord>> updatesByHost) { mPrunedApps = prunedApps; mUpdatesByProvider = updatesByProvider; mUpdatesByHost = updatesByHost; } @Nullable Set<String> getPrunedApps() { return mPrunedApps; } @Nullable SparseArray<List<BackupRestoreController.RestoreUpdateRecord>> getUpdatesByProvider() { return mUpdatesByProvider; } @Nullable SparseArray<List<BackupRestoreController.RestoreUpdateRecord>> getUpdatesByHost() { return mUpdatesByHost; } } } private class AppWidgetManagerLocal extends AppWidgetManagerInternal { Loading services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java +181 −0 Original line number Diff line number Diff line Loading @@ -22,17 +22,24 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.os.Build; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.SizeF; import android.util.Slog; import android.util.SparseArray; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; /** Loading Loading @@ -65,6 +72,16 @@ public class AppWidgetXmlUtil { private static final String ATTR_DESCRIPTION_RES = "description_res"; private static final String ATTR_PROVIDER_INHERITANCE = "provider_inheritance"; private static final String ATTR_OS_FINGERPRINT = "os_fingerprint"; static final String TAG_BACKUP_RESTORE_CONTROLLER_STATE = "br"; private static final String TAG_PRUNED_APPS = "pruned_apps"; private static final String ATTR_TAG = "tag"; private static final String ATTR_PACKAGE_NAMES = "pkgs"; private static final String TAG_PROVIDER_UPDATES = "provider_updates"; private static final String TAG_HOST_UPDATES = "host_updates"; private static final String TAG_RECORD = "record"; private static final String ATTR_OLD_ID = "old_id"; private static final String ATTR_NEW_ID = "new_id"; private static final String ATTR_NOTIFIED = "notified"; private static final String SIZE_SEPARATOR = ","; /** Loading Loading @@ -165,4 +182,168 @@ public class AppWidgetXmlUtil { return null; } } /** * Persists {@link AppWidgetServiceImpl.BackupRestoreController.State} to disk as XML. * See {@link #readBackupRestoreControllerState(TypedXmlPullParser)} for example XML. * * @param out XML serializer * @param state {@link AppWidgetServiceImpl.BackupRestoreController.State} of * intermediate states to be persisted as xml to resume restore after reboot. */ static void writeBackupRestoreControllerState( @NonNull final TypedXmlSerializer out, @NonNull final AppWidgetServiceImpl.BackupRestoreController.State state) throws IOException { Objects.requireNonNull(out); Objects.requireNonNull(state); out.startTag(null, TAG_BACKUP_RESTORE_CONTROLLER_STATE); final Set<String> prunedApps = state.getPrunedApps(); if (prunedApps != null && !prunedApps.isEmpty()) { out.startTag(null, TAG_PRUNED_APPS); out.attribute(null, ATTR_PACKAGE_NAMES, String.join(",", prunedApps)); out.endTag(null, TAG_PRUNED_APPS); } final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> updatesByProvider = state.getUpdatesByProvider(); if (updatesByProvider != null) { writeUpdateRecords(out, TAG_PROVIDER_UPDATES, updatesByProvider); } final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> updatesByHost = state.getUpdatesByHost(); if (updatesByHost != null) { writeUpdateRecords(out, TAG_HOST_UPDATES, updatesByHost); } out.endTag(null, TAG_BACKUP_RESTORE_CONTROLLER_STATE); } private static void writeUpdateRecords(@NonNull final TypedXmlSerializer out, @NonNull final String outerTag, @NonNull final SparseArray<List< AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> records) throws IOException { for (int i = 0; i < records.size(); i++) { final int tag = records.keyAt(i); out.startTag(null, outerTag); out.attributeInt(null, ATTR_TAG, tag); final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> entries = records.get(tag); for (AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord entry : entries) { out.startTag(null, TAG_RECORD); out.attributeInt(null, ATTR_OLD_ID, entry.oldId); out.attributeInt(null, ATTR_NEW_ID, entry.newId); out.attributeBoolean(null, ATTR_NOTIFIED, entry.notified); out.endTag(null, TAG_RECORD); } out.endTag(null, outerTag); } } /** * Parses {@link AppWidgetServiceImpl.BackupRestoreController.State} from xml. * * <pre> * {@code * <?xml version="1.0"?> * <br> * <pruned_apps pkgs="com.example.app1,com.example.app2,com.example.app3" /> * <provider_updates tag="0"> * <record old_id="10" new_id="0" notified="false" /> * </provider_updates> * <provider_updates tag="1"> * <record old_id="9" new_id="1" notified="true" /> * </provider_updates> * <provider_updates tag="2"> * <record old_id="8" new_id="2" notified="false" /> * </provider_updates> * <host_updates tag="0"> * <record old_id="10" new_id="0" notified="false" /> * </host_updates> * <host_updates tag="1"> * <record old_id="9" new_id="1" notified="true" /> * </host_updates> * <host_updates tag="2"> * <record old_id="8" new_id="2" notified="false" /> * </host_updates> * </br> * } * </pre> * * @param parser XML parser * @return {@link AppWidgetServiceImpl.BackupRestoreController.State} of intermediate states * in {@link AppWidgetServiceImpl.BackupRestoreController}, so that backup & restore can be * resumed after reboot. */ @Nullable static AppWidgetServiceImpl.BackupRestoreController.State readBackupRestoreControllerState(@NonNull final TypedXmlPullParser parser) { Objects.requireNonNull(parser); int type; String tag = null; final Set<String> prunedApps = new ArraySet<>(1); final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> updatesByProviders = new SparseArray<>(); final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> updatesByHosts = new SparseArray<>(); try { do { type = parser.next(); if (type != XmlPullParser.START_TAG) { continue; } tag = parser.getName(); switch (tag) { case TAG_PRUNED_APPS: final String packages = parser.getAttributeValue(null, ATTR_PACKAGE_NAMES); prunedApps.addAll(Arrays.asList(packages.split(","))); break; case TAG_PROVIDER_UPDATES: updatesByProviders.put(parser.getAttributeInt(null, ATTR_TAG), parseRestoreUpdateRecords(parser)); break; case TAG_HOST_UPDATES: updatesByHosts.put(parser.getAttributeInt(null, ATTR_TAG), parseRestoreUpdateRecords(parser)); break; default: break; } } while (type != XmlPullParser.END_DOCUMENT && (!TAG_BACKUP_RESTORE_CONTROLLER_STATE.equals(tag) || type != XmlPullParser.END_TAG)); } catch (IOException | XmlPullParserException e) { Log.e(TAG, "error parsing state", e); return null; } return new AppWidgetServiceImpl.BackupRestoreController.State( prunedApps, updatesByProviders, updatesByHosts); } @NonNull private static List< AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord > parseRestoreUpdateRecords(@NonNull final TypedXmlPullParser parser) throws XmlPullParserException, IOException { int type; String tag; final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> ret = new ArrayList<>(); do { type = parser.next(); tag = parser.getName(); if (tag.equals(TAG_RECORD) && type == XmlPullParser.START_TAG) { final int oldId = parser.getAttributeInt(null, ATTR_OLD_ID); final int newId = parser.getAttributeInt(null, ATTR_NEW_ID); final boolean notified = parser.getAttributeBoolean( null, ATTR_NOTIFIED); final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord record = new AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord( oldId, newId); record.notified = notified; ret.add(record); } } while (tag.equals(TAG_RECORD)); return ret; } } services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java +125 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +163 −22 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.appwidget; import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath; import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot; import static android.content.Context.KEYGUARD_SERVICE; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; Loading Loading @@ -166,6 +167,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.LongSupplier; class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider, Loading Loading @@ -457,7 +460,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku break; case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: added = true; // Follow through // fall through case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE: pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); break; Loading Loading @@ -3571,6 +3574,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } out.endTag(null, "gs"); if (supportResumeRestoreAfterReboot() && mBackupRestoreController.requiresPersistenceLocked()) { AppWidgetXmlUtil.writeBackupRestoreControllerState( out, mBackupRestoreController.getStateLocked(userId)); } out.endDocument(); return true; } catch (IOException e) { Loading Loading @@ -3717,6 +3727,32 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku LoadedWidgetState loadedWidgets = new LoadedWidgetState(widget, hostTag, providerTag); outLoadedWidgets.add(loadedWidgets); } else if (supportResumeRestoreAfterReboot() && AppWidgetXmlUtil.TAG_BACKUP_RESTORE_CONTROLLER_STATE.equals(tag)) { final BackupRestoreController.State s = AppWidgetXmlUtil.readBackupRestoreControllerState(parser); if (s == null) { continue; } final Set<String> prunedAppsInFile = s.getPrunedApps(); if (prunedAppsInFile != null) { final Set<String> prunedAppsInMemory = mBackupRestoreController .mPrunedAppsPerUser.get(userId); if (prunedAppsInMemory == null) { mBackupRestoreController.mPrunedAppsPerUser.put( userId, prunedAppsInFile); } else { prunedAppsInMemory.addAll(prunedAppsInFile); } } loadUpdateRecords(s.getUpdatesByProvider(), this::findProviderByTag, mBackupRestoreController.mUpdatesByProvider::get, mBackupRestoreController.mUpdatesByProvider::put); loadUpdateRecords(s.getUpdatesByHost(), this::findHostByTag, mBackupRestoreController.mUpdatesByHost::get, mBackupRestoreController.mUpdatesByHost::put); } } } while (type != XmlPullParser.END_DOCUMENT); Loading @@ -3732,6 +3768,36 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return version; } private <T> void loadUpdateRecords( @Nullable final SparseArray< List<BackupRestoreController.RestoreUpdateRecord>> updatesOnFile, @NonNull final Function<Integer, T> findKeyByTagCb, @NonNull final Function<T, List< BackupRestoreController.RestoreUpdateRecord>> findRecordsCb, @NonNull final BiConsumer<T, List< BackupRestoreController.RestoreUpdateRecord>> newRecordsCb) { if (updatesOnFile == null) { return; } for (int i = 0; i < updatesOnFile.size(); i++) { final int tag = updatesOnFile.keyAt(i); final List< BackupRestoreController.RestoreUpdateRecord > recordsOnFile = updatesOnFile.get(tag); if (recordsOnFile == null || recordsOnFile.isEmpty()) { continue; } final T key = findKeyByTagCb.apply(tag); final List<BackupRestoreController.RestoreUpdateRecord> recordsInMemory = findRecordsCb.apply(key); if (recordsInMemory != null) { recordsInMemory.addAll(recordsOnFile); } else { newRecordsCb.accept(key, recordsOnFile); } } } private void performUpgradeLocked(int fromVersion) { if (fromVersion < CURRENT_VERSION) { Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to " Loading Loading @@ -4674,7 +4740,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } private static final class Provider { static final class Provider { ProviderId id; AppWidgetProviderInfo info; Loading Loading @@ -4931,7 +4997,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } private static final class Host { static final class Host { HostId id; ArrayList<Widget> widgets = new ArrayList<>(); IAppWidgetHost callbacks; Loading Loading @@ -5250,10 +5316,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku /** * This class encapsulates the backup and restore logic for a user group state. */ private final class BackupRestoreController { final class BackupRestoreController { private static final String TAG = "BackupRestoreController"; private static final boolean DEBUG = true; private static final boolean DEBUG = AppWidgetServiceImpl.DEBUG; // Version of backed-up widget state. private static final int WIDGET_STATE_VERSION = 2; Loading @@ -5262,16 +5328,31 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // a given package. Keep track of what we've done so far here; the list is // cleared at the start of every system restore pass, but preserved through // any install-time restore operations. @GuardedBy("AppWidgetServiceImpl.this.mLock") private final SparseArray<Set<String>> mPrunedAppsPerUser = new SparseArray<>(); private final HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider = new HashMap<>(); private final HashMap<Host, ArrayList<RestoreUpdateRecord>> mUpdatesByHost = new HashMap<>(); @GuardedBy("AppWidgetServiceImpl.this.mLock") final Map<Provider, List<RestoreUpdateRecord>> mUpdatesByProvider = new ArrayMap<>(); @GuardedBy("mLock") @GuardedBy("AppWidgetServiceImpl.this.mLock") private final Map<Host, List<RestoreUpdateRecord>> mUpdatesByHost = new ArrayMap<>(); @GuardedBy("AppWidgetServiceImpl.this.mLock") private boolean mHasSystemRestoreFinished; @GuardedBy("AppWidgetServiceImpl.this.mLock") public boolean requiresPersistenceLocked() { if (mHasSystemRestoreFinished) { // No need to persist intermediate states if system restore is already finished. return false; } // If either of the internal states is non-empty, then we need to persist that return !(mPrunedAppsPerUser.size() == 0 && mUpdatesByProvider.isEmpty() && mUpdatesByHost.isEmpty()); } public List<String> getWidgetParticipants(int userId) { if (DEBUG) { Slog.i(TAG, "Getting widget participants for user: " + userId); Loading Loading @@ -5436,7 +5517,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // If there's no live entry for this provider, add an inactive one // so that widget IDs referring to them can be properly allocated // Backup and resotre only for the parent profile. // Backup and restore only for the parent profile. ComponentName componentName = new ComponentName(pkg, cl); Provider p = findProviderLocked(componentName, userId); Loading Loading @@ -5579,9 +5660,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku final UserHandle userHandle = new UserHandle(userId); // Build the providers' broadcasts and send them off Set<Map.Entry<Provider, ArrayList<RestoreUpdateRecord>>> providerEntries Set<Map.Entry<Provider, List<RestoreUpdateRecord>>> providerEntries = mUpdatesByProvider.entrySet(); for (Map.Entry<Provider, ArrayList<RestoreUpdateRecord>> e : providerEntries) { for (Map.Entry<Provider, List<RestoreUpdateRecord>> e : providerEntries) { // For each provider there's a list of affected IDs Provider provider = e.getKey(); if (provider.zombie) { Loading @@ -5589,7 +5670,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // We'll be called again when the provider is installed. continue; } ArrayList<RestoreUpdateRecord> updates = e.getValue(); List<RestoreUpdateRecord> updates = e.getValue(); final int pending = countPendingUpdates(updates); if (DEBUG) { Slog.i(TAG, "Provider " + provider + " pending: " + pending); Loading Loading @@ -5618,12 +5699,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } // same thing per host Set<Map.Entry<Host, ArrayList<RestoreUpdateRecord>>> hostEntries Set<Map.Entry<Host, List<RestoreUpdateRecord>>> hostEntries = mUpdatesByHost.entrySet(); for (Map.Entry<Host, ArrayList<RestoreUpdateRecord>> e : hostEntries) { for (Map.Entry<Host, List<RestoreUpdateRecord>> e : hostEntries) { Host host = e.getKey(); if (host.id.uid != UNKNOWN_UID) { ArrayList<RestoreUpdateRecord> updates = e.getValue(); List<RestoreUpdateRecord> updates = e.getValue(); final int pending = countPendingUpdates(updates); if (DEBUG) { Slog.i(TAG, "Host " + host + " pending: " + pending); Loading Loading @@ -5714,8 +5795,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return false; } @GuardedBy("mLock") private void stashProviderRestoreUpdateLocked(Provider provider, int oldId, int newId) { ArrayList<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider); List<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider); if (r == null) { r = new ArrayList<>(); mUpdatesByProvider.put(provider, r); Loading @@ -5732,7 +5814,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku r.add(new RestoreUpdateRecord(oldId, newId)); } private boolean alreadyStashed(ArrayList<RestoreUpdateRecord> stash, private boolean alreadyStashed(List<RestoreUpdateRecord> stash, final int oldId, final int newId) { final int N = stash.size(); for (int i = 0; i < N; i++) { Loading @@ -5744,8 +5826,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return false; } @GuardedBy("mLock") private void stashHostRestoreUpdateLocked(Host host, int oldId, int newId) { ArrayList<RestoreUpdateRecord> r = mUpdatesByHost.get(host); List<RestoreUpdateRecord> r = mUpdatesByHost.get(host); if (r == null) { r = new ArrayList<>(); mUpdatesByHost.put(host, r); Loading Loading @@ -5835,7 +5918,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku || widget.provider.getUserId() == userId); } private int countPendingUpdates(ArrayList<RestoreUpdateRecord> updates) { private int countPendingUpdates(List<RestoreUpdateRecord> updates) { int pending = 0; final int N = updates.size(); for (int i = 0; i < N; i++) { Loading @@ -5847,9 +5930,28 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return pending; } @GuardedBy("mLock") @NonNull private State getStateLocked(final int userId) { final Set<String> prunedApps = mPrunedAppsPerUser.get(userId); final SparseArray<List<RestoreUpdateRecord>> updatesByProvider = new SparseArray<>(); final SparseArray<List<RestoreUpdateRecord>> updatesByHost = new SparseArray<>(); mUpdatesByProvider.forEach((p, updates) -> { if (p.getUserId() == userId) { updatesByProvider.put(p.tag, new ArrayList<>(updates)); } }); mUpdatesByHost.forEach((h, updates) -> { if (h.getUserId() == userId) { updatesByHost.put(h.tag, new ArrayList<>(updates)); } }); return new State(prunedApps, updatesByProvider, updatesByHost); } // Accumulate a list of updates that affect the given provider for a final // coalesced notification broadcast once restore is over. private class RestoreUpdateRecord { static class RestoreUpdateRecord { public int oldId; public int newId; public boolean notified; Loading @@ -5860,6 +5962,45 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku notified = false; } } static final class State { // We need to make sure to wipe the pre-restore widget state only once for // a given package. Keep track of what we've done so far here; the list is // cleared at the start of every system restore pass, but preserved through // any install-time restore operations. @Nullable private final Set<String> mPrunedApps; @Nullable private final SparseArray<List<RestoreUpdateRecord>> mUpdatesByProvider; @Nullable private final SparseArray<List<RestoreUpdateRecord>> mUpdatesByHost; State( @Nullable final Set<String> prunedApps, @Nullable final SparseArray<List<RestoreUpdateRecord>> updatesByProvider, @Nullable final SparseArray<List<RestoreUpdateRecord>> updatesByHost) { mPrunedApps = prunedApps; mUpdatesByProvider = updatesByProvider; mUpdatesByHost = updatesByHost; } @Nullable Set<String> getPrunedApps() { return mPrunedApps; } @Nullable SparseArray<List<BackupRestoreController.RestoreUpdateRecord>> getUpdatesByProvider() { return mUpdatesByProvider; } @Nullable SparseArray<List<BackupRestoreController.RestoreUpdateRecord>> getUpdatesByHost() { return mUpdatesByHost; } } } private class AppWidgetManagerLocal extends AppWidgetManagerInternal { Loading
services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java +181 −0 Original line number Diff line number Diff line Loading @@ -22,17 +22,24 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.os.Build; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.SizeF; import android.util.Slog; import android.util.SparseArray; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; /** Loading Loading @@ -65,6 +72,16 @@ public class AppWidgetXmlUtil { private static final String ATTR_DESCRIPTION_RES = "description_res"; private static final String ATTR_PROVIDER_INHERITANCE = "provider_inheritance"; private static final String ATTR_OS_FINGERPRINT = "os_fingerprint"; static final String TAG_BACKUP_RESTORE_CONTROLLER_STATE = "br"; private static final String TAG_PRUNED_APPS = "pruned_apps"; private static final String ATTR_TAG = "tag"; private static final String ATTR_PACKAGE_NAMES = "pkgs"; private static final String TAG_PROVIDER_UPDATES = "provider_updates"; private static final String TAG_HOST_UPDATES = "host_updates"; private static final String TAG_RECORD = "record"; private static final String ATTR_OLD_ID = "old_id"; private static final String ATTR_NEW_ID = "new_id"; private static final String ATTR_NOTIFIED = "notified"; private static final String SIZE_SEPARATOR = ","; /** Loading Loading @@ -165,4 +182,168 @@ public class AppWidgetXmlUtil { return null; } } /** * Persists {@link AppWidgetServiceImpl.BackupRestoreController.State} to disk as XML. * See {@link #readBackupRestoreControllerState(TypedXmlPullParser)} for example XML. * * @param out XML serializer * @param state {@link AppWidgetServiceImpl.BackupRestoreController.State} of * intermediate states to be persisted as xml to resume restore after reboot. */ static void writeBackupRestoreControllerState( @NonNull final TypedXmlSerializer out, @NonNull final AppWidgetServiceImpl.BackupRestoreController.State state) throws IOException { Objects.requireNonNull(out); Objects.requireNonNull(state); out.startTag(null, TAG_BACKUP_RESTORE_CONTROLLER_STATE); final Set<String> prunedApps = state.getPrunedApps(); if (prunedApps != null && !prunedApps.isEmpty()) { out.startTag(null, TAG_PRUNED_APPS); out.attribute(null, ATTR_PACKAGE_NAMES, String.join(",", prunedApps)); out.endTag(null, TAG_PRUNED_APPS); } final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> updatesByProvider = state.getUpdatesByProvider(); if (updatesByProvider != null) { writeUpdateRecords(out, TAG_PROVIDER_UPDATES, updatesByProvider); } final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> updatesByHost = state.getUpdatesByHost(); if (updatesByHost != null) { writeUpdateRecords(out, TAG_HOST_UPDATES, updatesByHost); } out.endTag(null, TAG_BACKUP_RESTORE_CONTROLLER_STATE); } private static void writeUpdateRecords(@NonNull final TypedXmlSerializer out, @NonNull final String outerTag, @NonNull final SparseArray<List< AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> records) throws IOException { for (int i = 0; i < records.size(); i++) { final int tag = records.keyAt(i); out.startTag(null, outerTag); out.attributeInt(null, ATTR_TAG, tag); final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> entries = records.get(tag); for (AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord entry : entries) { out.startTag(null, TAG_RECORD); out.attributeInt(null, ATTR_OLD_ID, entry.oldId); out.attributeInt(null, ATTR_NEW_ID, entry.newId); out.attributeBoolean(null, ATTR_NOTIFIED, entry.notified); out.endTag(null, TAG_RECORD); } out.endTag(null, outerTag); } } /** * Parses {@link AppWidgetServiceImpl.BackupRestoreController.State} from xml. * * <pre> * {@code * <?xml version="1.0"?> * <br> * <pruned_apps pkgs="com.example.app1,com.example.app2,com.example.app3" /> * <provider_updates tag="0"> * <record old_id="10" new_id="0" notified="false" /> * </provider_updates> * <provider_updates tag="1"> * <record old_id="9" new_id="1" notified="true" /> * </provider_updates> * <provider_updates tag="2"> * <record old_id="8" new_id="2" notified="false" /> * </provider_updates> * <host_updates tag="0"> * <record old_id="10" new_id="0" notified="false" /> * </host_updates> * <host_updates tag="1"> * <record old_id="9" new_id="1" notified="true" /> * </host_updates> * <host_updates tag="2"> * <record old_id="8" new_id="2" notified="false" /> * </host_updates> * </br> * } * </pre> * * @param parser XML parser * @return {@link AppWidgetServiceImpl.BackupRestoreController.State} of intermediate states * in {@link AppWidgetServiceImpl.BackupRestoreController}, so that backup & restore can be * resumed after reboot. */ @Nullable static AppWidgetServiceImpl.BackupRestoreController.State readBackupRestoreControllerState(@NonNull final TypedXmlPullParser parser) { Objects.requireNonNull(parser); int type; String tag = null; final Set<String> prunedApps = new ArraySet<>(1); final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> updatesByProviders = new SparseArray<>(); final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> updatesByHosts = new SparseArray<>(); try { do { type = parser.next(); if (type != XmlPullParser.START_TAG) { continue; } tag = parser.getName(); switch (tag) { case TAG_PRUNED_APPS: final String packages = parser.getAttributeValue(null, ATTR_PACKAGE_NAMES); prunedApps.addAll(Arrays.asList(packages.split(","))); break; case TAG_PROVIDER_UPDATES: updatesByProviders.put(parser.getAttributeInt(null, ATTR_TAG), parseRestoreUpdateRecords(parser)); break; case TAG_HOST_UPDATES: updatesByHosts.put(parser.getAttributeInt(null, ATTR_TAG), parseRestoreUpdateRecords(parser)); break; default: break; } } while (type != XmlPullParser.END_DOCUMENT && (!TAG_BACKUP_RESTORE_CONTROLLER_STATE.equals(tag) || type != XmlPullParser.END_TAG)); } catch (IOException | XmlPullParserException e) { Log.e(TAG, "error parsing state", e); return null; } return new AppWidgetServiceImpl.BackupRestoreController.State( prunedApps, updatesByProviders, updatesByHosts); } @NonNull private static List< AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord > parseRestoreUpdateRecords(@NonNull final TypedXmlPullParser parser) throws XmlPullParserException, IOException { int type; String tag; final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> ret = new ArrayList<>(); do { type = parser.next(); tag = parser.getName(); if (tag.equals(TAG_RECORD) && type == XmlPullParser.START_TAG) { final int oldId = parser.getAttributeInt(null, ATTR_OLD_ID); final int newId = parser.getAttributeInt(null, ATTR_NEW_ID); final boolean notified = parser.getAttributeBoolean( null, ATTR_NOTIFIED); final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord record = new AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord( oldId, newId); record.notified = notified; ret.add(record); } } while (tag.equals(TAG_RECORD)); return ret; } }
services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java +125 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes