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

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

Merge "Use a one-off thread to load launch params" into main

parents 6c8fbe78 1c9612e6
Loading
Loading
Loading
Loading
+163 −100
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManagerInternal;
import android.graphics.Rect;
import android.os.Environment;
import android.os.Process;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -50,6 +51,9 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.function.IntFunction;

/**
@@ -83,6 +87,12 @@ class LaunchParamsPersister {

    private PackageList mPackageList;

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

    /**
     * A dual layer map that first maps user ID to a secondary map, which maps component name (the
     * launching activity of tasks) to {@link PersistableLaunchParams} that stores launch metadata
@@ -117,113 +127,33 @@ 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 (mLoadingTaskMap.contains(userId)) {
            Slog.e(TAG, "Duplicated 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 String tagName = parser.getName();
                    if (!TAG_LAUNCH_PARAMS.equals(tagName)) {
                        Slog.w(TAG, "Unexpected tag name: " + tagName);
                        continue;
        final LoadingTask task = new LoadingTask(userId);
        mLoadingTaskMap.put(userId, task);
        task.execute();
    }

                    params.restore(paramsFile, parser);
    void onCleanupUser(int userId) {
        // There is no need to abort the task itself. Just let the loading task finish silently
        // without modifying any state.
        mLoadingTaskMap.remove(userId);
        mLaunchParamsMap.remove(userId);
    }

                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);
            }
    private void waitAndMoveResultIfLoading(int userId) {
        final LoadingTask task = mLoadingTaskMap.removeReturnOld(userId);
        if (task == null) {
            return;
        }

        if (!filesToDelete.isEmpty()) {
            mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true);
        final ArrayMap<ComponentName, PersistableLaunchParams> map = task.get();
        if (map == null) {
            return;
        }
        mLaunchParamsMap.put(userId, map);
    }

    void saveTask(Task task) {
@@ -236,6 +166,7 @@ class LaunchParamsPersister {
            return;
        }
        final int userId = task.mUserId;
        waitAndMoveResultIfLoading(userId);
        PersistableLaunchParams params;
        ArrayMap<ComponentName, PersistableLaunchParams> map = mLaunchParamsMap.get(userId);
        if (map == null) {
@@ -297,6 +228,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;
        waitAndMoveResultIfLoading(userId);
        final String windowLayoutAffinity;
        if (task != null) {
            windowLayoutAffinity = task.mWindowLayoutAffinity;
@@ -394,6 +326,137 @@ class LaunchParamsPersister {
        }
    }

    private class LoadingTask
            implements Callable<ArrayMap<ComponentName, PersistableLaunchParams>> {
        private final int mUserId;
        private final FutureTask<ArrayMap<ComponentName, PersistableLaunchParams>> mFutureTask;

        private LoadingTask(int userId) {
            mUserId = userId;
            mFutureTask = new FutureTask<>(this);
        }

        private void execute() {
            new Thread(mFutureTask).start();
        }

        private ArrayMap<ComponentName, PersistableLaunchParams> get() {
            try {
                return mFutureTask.get();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (ExecutionException e) {
                Slog.e(TAG, "Failed to load launch params for user#" + mUserId, e);
                return null;
            }
        }

        @Override
        public ArrayMap<ComponentName, PersistableLaunchParams> call() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

            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 null;
            }

            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);
                }
            }

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

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

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

        private CleanUpComponentQueueItem(List<File> componentFiles) {
@@ -483,7 +546,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";
+15 −0
Original line number Diff line number Diff line
@@ -494,6 +494,21 @@ public class LaunchParamsPersisterTests extends WindowTestsBase {
        assertTrue("Result should be empty.", mResult.isEmpty());
    }

    @Test
    public void testAbortsLoadingWhenUserCleansUpBeforeLoadingFinishes() {
        mTarget.saveTask(mTestTask);
        mPersisterQueue.flush();

        final LaunchParamsPersister target = new LaunchParamsPersister(mPersisterQueue, mSupervisor,
                mUserFolderGetter);
        target.onSystemReady();
        target.onUnlockUser(TEST_USER_ID);
        target.onCleanupUser(TEST_USER_ID);

        target.getLaunchParams(mTestTask, null, mResult);
        assertTrue("Result should be empty.", mResult.isEmpty());
    }

    private static boolean deleteRecursively(File file) {
        boolean result = true;
        if (file.isDirectory()) {