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

Commit 000e0e33 authored by Pinyao Ting's avatar Pinyao Ting Committed by Android (Google) Code Review
Browse files

Merge "Write/read intermediate states during widget restore" into main

parents f25716a4 8c5038b8
Loading
Loading
Loading
Loading
+163 −22
Original line number Diff line number Diff line
@@ -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;
@@ -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,
@@ -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;
@@ -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) {
@@ -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);
@@ -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 "
@@ -4674,7 +4740,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        }
    }

    private static final class Provider {
    static final class Provider {

        ProviderId id;
        AppWidgetProviderInfo info;
@@ -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;
@@ -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;
@@ -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);
@@ -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);
@@ -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) {
@@ -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);
@@ -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);
@@ -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);
@@ -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++) {
@@ -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);
@@ -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++) {
@@ -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;
@@ -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 {
+181 −0
Original line number Diff line number Diff line
@@ -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;

/**
@@ -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 = ",";

    /**
@@ -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;
    }
}
+125 −0

File changed.

Preview size limit exceeded, changes collapsed.