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

Commit d6e6c54b authored by Garfield Tan's avatar Garfield Tan Committed by Android (Google) Code Review
Browse files

Merge "Send loading tasks to PersisterQueue" into main

parents 67350f4c 696553cf
Loading
Loading
Loading
Loading
+180 −102
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.function.IntFunction;

/**
@@ -91,6 +92,12 @@ class LaunchParamsPersister {
    private final SparseArray<ArrayMap<ComponentName, PersistableLaunchParams>> mLaunchParamsMap =
            new SparseArray<>();

    /**
     * A map from user ID to the active {@link LoadingQueueItem} user when we're loading the launch
     * params for that user.
     */
    private final SparseArray<LoadingQueueItem> mLoadingItemMap = new SparseArray<>();

    /**
     * A map from {@link android.content.pm.ActivityInfo.WindowLayout#windowLayoutAffinity} to
     * activity's component name for reverse queries from window layout affinities to activities.
@@ -117,112 +124,30 @@ class LaunchParamsPersister {
    }

    void onUnlockUser(int userId) {
        loadLaunchParams(userId);
    }

    void onCleanupUser(int userId) {
        mLaunchParamsMap.remove(userId);
    }

    private void loadLaunchParams(int userId) {
        final List<File> filesToDelete = new ArrayList<>();
        final File launchParamsFolder = getLaunchParamFolder(userId);
        if (!launchParamsFolder.isDirectory()) {
            Slog.i(TAG, "Didn't find launch param folder for user " + userId);
        if (mLoadingItemMap.contains(userId)) {
            Slog.e(TAG, "Duplicate onUnlockUser " + userId);
            return;
        }

        final Set<String> packages = new ArraySet<>(mPackageList.getPackageNames());

        final File[] paramsFiles = launchParamsFolder.listFiles();
        final ArrayMap<ComponentName, PersistableLaunchParams> map =
                new ArrayMap<>(paramsFiles.length);
        mLaunchParamsMap.put(userId, map);

        for (File paramsFile : paramsFiles) {
            if (!paramsFile.isFile()) {
                Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file.");
                continue;
            }
            if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) {
                Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName());
                filesToDelete.add(paramsFile);
                continue;
            }
            String paramsFileName = paramsFile.getName();
            // Migrate all records from old separator to new separator.
            final int oldSeparatorIndex =
                    paramsFileName.indexOf(OLD_ESCAPED_COMPONENT_SEPARATOR);
            if (oldSeparatorIndex != -1) {
                if (paramsFileName.indexOf(
                        OLD_ESCAPED_COMPONENT_SEPARATOR, oldSeparatorIndex + 1) != -1) {
                    // Rare case. We have more than one old escaped component separator probably
                    // because this app uses underscore in their package name. We can't distinguish
                    // which one is the real separator so let's skip it.
                    filesToDelete.add(paramsFile);
                    continue;
                }
                paramsFileName = paramsFileName.replace(
                        OLD_ESCAPED_COMPONENT_SEPARATOR, ESCAPED_COMPONENT_SEPARATOR);
                final File newFile = new File(launchParamsFolder, paramsFileName);
                if (paramsFile.renameTo(newFile)) {
                    paramsFile = newFile;
                } else {
                    // Rare case. For some reason we can't rename the file. Let's drop this record
                    // instead.
                    filesToDelete.add(paramsFile);
                    continue;
                }
            }
            final String componentNameString = paramsFileName.substring(
                    0 /* beginIndex */,
                    paramsFileName.length() - LAUNCH_PARAMS_FILE_SUFFIX.length())
                    .replace(ESCAPED_COMPONENT_SEPARATOR, ORIGINAL_COMPONENT_SEPARATOR);
            final ComponentName name = ComponentName.unflattenFromString(
                    componentNameString);
            if (name == null) {
                Slog.w(TAG, "Unexpected file name: " + paramsFileName);
                filesToDelete.add(paramsFile);
                continue;
            }

            if (!packages.contains(name.getPackageName())) {
                // Rare case. PersisterQueue doesn't have a chance to remove files for removed
                // packages last time.
                filesToDelete.add(paramsFile);
                continue;
            }

            try (InputStream in = new FileInputStream(paramsFile)) {
                final PersistableLaunchParams params = new PersistableLaunchParams();
                final TypedXmlPullParser parser = Xml.resolvePullParser(in);
                int event;
                while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
                        && event != XmlPullParser.END_TAG) {
                    if (event != XmlPullParser.START_TAG) {
                        continue;
        final LoadingQueueItem item = new LoadingQueueItem(userId);
        mLoadingItemMap.put(userId, item);
        mPersisterQueue.addItem(item, /* flush */ false);
    }

                    final String tagName = parser.getName();
                    if (!TAG_LAUNCH_PARAMS.equals(tagName)) {
                        Slog.w(TAG, "Unexpected tag name: " + tagName);
                        continue;
                    }

                    params.restore(paramsFile, parser);
                }
    void onCleanupUser(int userId) {
        final LoadingQueueItem item = mLoadingItemMap.removeReturnOld(userId);
        if (item != null) {
            item.abort();

                map.put(name, params);
                addComponentNameToLaunchParamAffinityMapIfNotNull(
                        name, params.mWindowLayoutAffinity);
            } catch (Exception e) {
                Slog.w(TAG, "Failed to restore launch params for " + name, e);
                filesToDelete.add(paramsFile);
            mPersisterQueue.removeItems(
                    queueItem -> queueItem.mUserId == userId, LoadingQueueItem.class);
        }
        mLaunchParamsMap.remove(userId);
    }

        if (!filesToDelete.isEmpty()) {
            mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true);
    private void waitForLoading(int userId) {
        final LoadingQueueItem item = mLoadingItemMap.get(userId);
        if (item != null) {
            item.waitUntilFinish();
        }
    }

@@ -236,6 +161,7 @@ class LaunchParamsPersister {
            return;
        }
        final int userId = task.mUserId;
        waitForLoading(userId);
        PersistableLaunchParams params;
        ArrayMap<ComponentName, PersistableLaunchParams> map = mLaunchParamsMap.get(userId);
        if (map == null) {
@@ -297,6 +223,7 @@ class LaunchParamsPersister {
    void getLaunchParams(Task task, ActivityRecord activity, LaunchParams outParams) {
        final ComponentName name = task != null ? task.realActivity : activity.mActivityComponent;
        final int userId = task != null ? task.mUserId : activity.mUserId;
        waitForLoading(userId);
        final String windowLayoutAffinity;
        if (task != null) {
            windowLayoutAffinity = task.mWindowLayoutAffinity;
@@ -394,6 +321,156 @@ class LaunchParamsPersister {
        }
    }

    /**
     * The work item used to load launch parameters with {@link PersisterQueue} in a background
     * thread, so that we don't block the thread {@link com.android.server.am.UserController} uses
     * to broadcast user state changes for I/O operations. See b/365983567 for more details.
     */
    private class LoadingQueueItem implements PersisterQueue.QueueItem {
        private final int mUserId;
        private final CountDownLatch mLatch = new CountDownLatch(1);
        private boolean mAborted = false;

        private LoadingQueueItem(int userId) {
            mUserId = userId;
        }

        @Override
        public void process() {
            try {
                loadLaunchParams();
            } finally {
                synchronized (mSupervisor.mService.getGlobalLock()) {
                    mLoadingItemMap.remove(mUserId);
                    mLatch.countDown();
                }
            }
        }

        private void abort() {
            mAborted = true;
        }

        private void waitUntilFinish() {
            if (mAborted) {
                return;
            }

            try {
                mLatch.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        private void loadLaunchParams() {
            final List<File> filesToDelete = new ArrayList<>();
            final File launchParamsFolder = getLaunchParamFolder(mUserId);
            if (!launchParamsFolder.isDirectory()) {
                Slog.i(TAG, "Didn't find launch param folder for user " + mUserId);
                return;
            }

            final Set<String> packages = new ArraySet<>(mPackageList.getPackageNames());

            final File[] paramsFiles = launchParamsFolder.listFiles();
            final ArrayMap<ComponentName, PersistableLaunchParams> map =
                    new ArrayMap<>(paramsFiles.length);

            for (File paramsFile : paramsFiles) {
                if (!paramsFile.isFile()) {
                    Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file.");
                    continue;
                }
                if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) {
                    Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName());
                    filesToDelete.add(paramsFile);
                    continue;
                }
                String paramsFileName = paramsFile.getName();
                // Migrate all records from old separator to new separator.
                final int oldSeparatorIndex =
                        paramsFileName.indexOf(OLD_ESCAPED_COMPONENT_SEPARATOR);
                if (oldSeparatorIndex != -1) {
                    if (paramsFileName.indexOf(
                            OLD_ESCAPED_COMPONENT_SEPARATOR, oldSeparatorIndex + 1) != -1) {
                        // Rare case. We have more than one old escaped component separator probably
                        // because this app uses underscore in their package name. We can't
                        // distinguish which one is the real separator so let's skip it.
                        filesToDelete.add(paramsFile);
                        continue;
                    }
                    paramsFileName = paramsFileName.replace(
                            OLD_ESCAPED_COMPONENT_SEPARATOR, ESCAPED_COMPONENT_SEPARATOR);
                    final File newFile = new File(launchParamsFolder, paramsFileName);
                    if (paramsFile.renameTo(newFile)) {
                        paramsFile = newFile;
                    } else {
                        // Rare case. For some reason we can't rename the file. Let's drop this
                        // record instead.
                        filesToDelete.add(paramsFile);
                        continue;
                    }
                }
                final String componentNameString = paramsFileName.substring(
                                0 /* beginIndex */,
                                paramsFileName.length() - LAUNCH_PARAMS_FILE_SUFFIX.length())
                        .replace(ESCAPED_COMPONENT_SEPARATOR, ORIGINAL_COMPONENT_SEPARATOR);
                final ComponentName name = ComponentName.unflattenFromString(
                        componentNameString);
                if (name == null) {
                    Slog.w(TAG, "Unexpected file name: " + paramsFileName);
                    filesToDelete.add(paramsFile);
                    continue;
                }

                if (!packages.contains(name.getPackageName())) {
                    // Rare case. PersisterQueue doesn't have a chance to remove files for removed
                    // packages last time.
                    filesToDelete.add(paramsFile);
                    continue;
                }

                try (InputStream in = new FileInputStream(paramsFile)) {
                    final PersistableLaunchParams params = new PersistableLaunchParams();
                    final TypedXmlPullParser parser = Xml.resolvePullParser(in);
                    int event;
                    while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
                            && event != XmlPullParser.END_TAG) {
                        if (event != XmlPullParser.START_TAG) {
                            continue;
                        }

                        final String tagName = parser.getName();
                        if (!TAG_LAUNCH_PARAMS.equals(tagName)) {
                            Slog.w(TAG, "Unexpected tag name: " + tagName);
                            continue;
                        }

                        params.restore(paramsFile, parser);
                    }

                    map.put(name, params);
                    addComponentNameToLaunchParamAffinityMapIfNotNull(
                            name, params.mWindowLayoutAffinity);
                } catch (Exception e) {
                    Slog.w(TAG, "Failed to restore launch params for " + name, e);
                    filesToDelete.add(paramsFile);
                }
            }

            synchronized (mSupervisor.mService.getGlobalLock()) {
                if (!mAborted) {
                    mLaunchParamsMap.put(mUserId, map);
                }
            }

            if (!filesToDelete.isEmpty()) {
                mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true);
            }
        }
    }

    private class LaunchParamsWriteQueueItem
            implements PersisterQueue.WriteQueueItem<LaunchParamsWriteQueueItem> {
        private final int mUserId;
@@ -466,7 +543,8 @@ class LaunchParamsPersister {
        }
    }

    private class CleanUpComponentQueueItem implements PersisterQueue.WriteQueueItem {
    private static class CleanUpComponentQueueItem
            implements PersisterQueue.WriteQueueItem<CleanUpComponentQueueItem> {
        private final List<File> mComponentFiles;

        private CleanUpComponentQueueItem(List<File> componentFiles) {
@@ -483,7 +561,7 @@ class LaunchParamsPersister {
        }
    }

    private class PersistableLaunchParams {
    private static class PersistableLaunchParams {
        private static final String ATTR_WINDOWING_MODE = "windowing_mode";
        private static final String ATTR_DISPLAY_UNIQUE_ID = "display_unique_id";
        private static final String ATTR_BOUNDS = "bounds";
+40 −27
Original line number Diff line number Diff line
@@ -49,14 +49,16 @@ class PersisterQueue {
    /** Special value for mWriteTime to mean don't wait, just write */
    private static final long FLUSH_QUEUE = -1;

    /** An {@link WriteQueueItem} that doesn't do anything. Used to trigger {@link
     * Listener#onPreProcessItem}. */
    static final WriteQueueItem EMPTY_ITEM = () -> { };
    /**
     * A {@link QueueItem} that doesn't do anything. Used to trigger
     * {@link Listener#onPreProcessItem}.
     */
    static final QueueItem EMPTY_ITEM = () -> { };

    private final long mInterWriteDelayMs;
    private final long mPreTaskDelayMs;
    private final LazyTaskWriterThread mLazyTaskWriterThread;
    private final ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<>();
    private final ArrayList<QueueItem> mQueue = new ArrayList<>();

    private final ArrayList<Listener> mListeners = new ArrayList<>();

@@ -105,10 +107,10 @@ class PersisterQueue {
        mLazyTaskWriterThread.join();
    }

    synchronized void addItem(WriteQueueItem item, boolean flush) {
        mWriteQueue.add(item);
    synchronized void addItem(QueueItem item, boolean flush) {
        mQueue.add(item);

        if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
        if (flush || mQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
            mNextWriteTime = FLUSH_QUEUE;
        } else if (mNextWriteTime == 0) {
            mNextWriteTime = SystemClock.uptimeMillis() + mPreTaskDelayMs;
@@ -116,11 +118,12 @@ class PersisterQueue {
        notify();
    }

    synchronized <T extends WriteQueueItem> T findLastItem(Predicate<T> predicate, Class<T> clazz) {
        for (int i = mWriteQueue.size() - 1; i >= 0; --i) {
            WriteQueueItem writeQueueItem = mWriteQueue.get(i);
            if (clazz.isInstance(writeQueueItem)) {
                T item = clazz.cast(writeQueueItem);
    synchronized <T extends WriteQueueItem<T>> T findLastItem(Predicate<T> predicate,
            Class<T> clazz) {
        for (int i = mQueue.size() - 1; i >= 0; --i) {
            QueueItem queueItem = mQueue.get(i);
            if (clazz.isInstance(queueItem)) {
                T item = clazz.cast(queueItem);
                if (predicate.test(item)) {
                    return item;
                }
@@ -134,7 +137,7 @@ class PersisterQueue {
     * Updates the last item found in the queue that matches the given item, or adds it to the end
     * of the queue if no such item is found.
     */
    synchronized <T extends WriteQueueItem> void updateLastOrAddItem(T item, boolean flush) {
    synchronized <T extends WriteQueueItem<T>> void updateLastOrAddItem(T item, boolean flush) {
        final T itemToUpdate = findLastItem(item::matches, (Class<T>) item.getClass());
        if (itemToUpdate == null) {
            addItem(item, flush);
@@ -148,15 +151,15 @@ class PersisterQueue {
    /**
     * Removes all items with which given predicate returns {@code true}.
     */
    synchronized <T extends WriteQueueItem> void removeItems(Predicate<T> predicate,
    synchronized <T extends QueueItem> void removeItems(Predicate<T> predicate,
            Class<T> clazz) {
        for (int i = mWriteQueue.size() - 1; i >= 0; --i) {
            WriteQueueItem writeQueueItem = mWriteQueue.get(i);
            if (clazz.isInstance(writeQueueItem)) {
                T item = clazz.cast(writeQueueItem);
        for (int i = mQueue.size() - 1; i >= 0; --i) {
            QueueItem queueItem = mQueue.get(i);
            if (clazz.isInstance(queueItem)) {
                T item = clazz.cast(queueItem);
                if (predicate.test(item)) {
                    if (DEBUG) Slog.d(TAG, "Removing " + item + " from write queue.");
                    mWriteQueue.remove(i);
                    mQueue.remove(i);
                }
            }
        }
@@ -201,7 +204,7 @@ class PersisterQueue {
        // See https://b.corp.google.com/issues/64438652#comment7

        // If mNextWriteTime, then don't delay between each call to saveToXml().
        final WriteQueueItem item;
        final QueueItem item;
        synchronized (this) {
            if (mNextWriteTime != FLUSH_QUEUE) {
                // The next write we don't have to wait so long.
@@ -212,7 +215,7 @@ class PersisterQueue {
                }
            }

            while (mWriteQueue.isEmpty()) {
            while (mQueue.isEmpty()) {
                if (mNextWriteTime != 0) {
                    mNextWriteTime = 0; // idle.
                    notify(); // May need to wake up flush().
@@ -224,17 +227,18 @@ class PersisterQueue {
                }
                if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
                wait();
                // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS
                // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_TASK_DELAY_MS
                // from now.
            }
            item = mWriteQueue.remove(0);
            item = mQueue.remove(0);

            final boolean isWriteItem = item instanceof WriteQueueItem<?>;
            long now = SystemClock.uptimeMillis();
            if (DEBUG) {
                Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" + mNextWriteTime
                        + " mWriteQueue.size=" + mWriteQueue.size());
                        + " mWriteQueue.size=" + mQueue.size() + " isWriteItem=" + isWriteItem);
            }
            while (now < mNextWriteTime) {
            while (now < mNextWriteTime && isWriteItem) {
                if (DEBUG) {
                    Slog.d(TAG, "LazyTaskWriter: waiting " + (mNextWriteTime - now));
                }
@@ -248,9 +252,18 @@ class PersisterQueue {
        item.process();
    }

    interface WriteQueueItem<T extends WriteQueueItem<T>> {
    /**
     * An item the {@link PersisterQueue} processes. Used for loading tasks. Subclasses of this, but
     * not {@link WriteQueueItem}, aren't subject to waiting.
     */
    interface QueueItem {
        void process();
    }

    /**
     * A write item the {@link PersisterQueue} processes. Used for persisting tasks.
     */
    interface WriteQueueItem<T extends WriteQueueItem<T>> extends QueueItem {
        default void updateFrom(T item) {}

        default boolean matches(T item) {
@@ -288,7 +301,7 @@ class PersisterQueue {
                while (true) {
                    final boolean probablyDone;
                    synchronized (PersisterQueue.this) {
                        probablyDone = mWriteQueue.isEmpty();
                        probablyDone = mQueue.isEmpty();
                    }

                    for (int i = mListeners.size() - 1; i >= 0; --i) {
+61 −14

File changed.

Preview size limit exceeded, changes collapsed.

+65 −37

File changed.

Preview size limit exceeded, changes collapsed.