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

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

Merge "Decouple presubmit tests for shortcut manager from file system." into main

parents 4fe70687 c1948788
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -453,7 +453,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
    @Override
    protected File getShortcutPackageItemFile() {
        final File path = new File(mShortcutUser.mService.injectUserDataPath(
                mShortcutUser.getUserId()), ShortcutUser.DIRECTORY_LUANCHERS);
                mShortcutUser.getUserId()), ShortcutUser.DIRECTORY_LAUNCHERS);
        // Package user id and owner id can have different values for ShortcutLaunchers. Adding
        // user Id to the file name to create a unique path. Owner id is used in the root path.
        final String fileName = getPackageName() + getPackageUserId() + ".xml";
+85 −60
Original line number Diff line number Diff line
@@ -16,7 +16,9 @@
package com.android.server.pm;

import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;

import static com.android.server.pm.ShortcutUser.DIRECTORY_LAUNCHERS;
import static com.android.server.pm.ShortcutUser.DIRECTORY_PACKAGES;

import android.Manifest.permission;
import android.annotation.IntDef;
@@ -94,7 +96,6 @@ import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.text.format.TimeMigrationUtils;
import android.util.ArraySet;
@@ -112,7 +113,6 @@ import android.view.IWindowManager;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
@@ -155,7 +155,6 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * TODO:
@@ -171,7 +170,7 @@ public class ShortcutService extends IShortcutService.Stub {
    static final boolean DEBUG = false; // STOPSHIP if true
    static final boolean DEBUG_LOAD = false; // STOPSHIP if true
    static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
    static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE;
    static final boolean DEBUG_REBOOT = false; // STOPSHIP if true

    @VisibleForTesting
    static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
@@ -292,7 +291,8 @@ public class ShortcutService extends IShortcutService.Stub {

    final Context mContext;

    private final Object mServiceLock = new Object();
    @VisibleForTesting
    final Object mServiceLock = new Object();
    private final Object mNonPersistentUsersLock = new Object();
    private final Object mWtfLock = new Object();

@@ -982,7 +982,7 @@ public class ShortcutService extends IShortcutService.Stub {
    }

    @VisibleForTesting
    void saveBaseState() {
    void injectSaveBaseState() {
        try (ResilientAtomicFile file = getBaseStateFile()) {
            if (DEBUG || DEBUG_REBOOT) {
                Slog.d(TAG, "Saving to " + file.getBaseFile());
@@ -994,6 +994,19 @@ public class ShortcutService extends IShortcutService.Stub {
                    outs = file.startWrite();
                }

                saveBaseStateAsXml(outs);

                // Close.
                injectFinishWrite(file, outs);
            } catch (IOException e) {
                Slog.w(TAG, "Failed to write to file " + file.getBaseFile(), e);
                file.failWrite(outs);
            }
        }
    }

    @VisibleForTesting
    protected void saveBaseStateAsXml(OutputStream outs) throws IOException {
        // Write to XML
        TypedXmlSerializer out = Xml.resolveSerializer(outs);
        out.startDocument(null, true);
@@ -1006,20 +1019,18 @@ public class ShortcutService extends IShortcutService.Stub {
        // Epilogue.
        out.endTag(null, TAG_ROOT);
        out.endDocument();

                // Close.
                injectFinishWrite(file, outs);
            } catch (IOException e) {
                Slog.w(TAG, "Failed to write to file " + file.getBaseFile(), e);
                file.failWrite(outs);
            }
        }
    }

    @GuardedBy("mServiceLock")
    private void loadBaseStateLocked() {
        mRawLastResetTime.set(0);
        injectLoadBaseState();
        // Adjust the last reset time.
        getLastResetTimeLocked();
    }

    @VisibleForTesting
    protected void injectLoadBaseState() {
        try (ResilientAtomicFile file = getBaseStateFile()) {
            if (DEBUG || DEBUG_REBOOT) {
                Slog.d(TAG, "Loading from " + file.getBaseFile());
@@ -1030,7 +1041,21 @@ public class ShortcutService extends IShortcutService.Stub {
                if (in == null) {
                    throw new FileNotFoundException(file.getBaseFile().getAbsolutePath());
                }
                loadBaseStateAsXml(in);
            } catch (FileNotFoundException e) {
                // Use the default
            } catch (IOException | XmlPullParserException e) {
                // Remove corrupted file and retry.
                file.failRead(in, e);
                loadBaseStateLocked();
                return;
            }
        }
    }

    @VisibleForTesting
    protected void loadBaseStateAsXml(InputStream in)
            throws IOException, XmlPullParserException {
        TypedXmlPullParser parser = Xml.resolvePullParser(in);

        int type;
@@ -1058,17 +1083,6 @@ public class ShortcutService extends IShortcutService.Stub {
                    break;
            }
        }
            } catch (FileNotFoundException e) {
                // Use the default
            } catch (IOException | XmlPullParserException e) {
                // Remove corrupted file and retry.
                file.failRead(in, e);
                loadBaseStateLocked();
                return;
            }
        }
        // Adjust the last reset time.
        getLastResetTimeLocked();
    }

    @VisibleForTesting
@@ -1083,7 +1097,8 @@ public class ShortcutService extends IShortcutService.Stub {
                "user shortcut", null);
    }

    private void saveUser(@UserIdInt int userId) {
    @VisibleForTesting
    protected void injectSaveUser(@UserIdInt int userId) {
        try (ResilientAtomicFile file = getUserFile(userId)) {
            FileOutputStream os = null;
            try {
@@ -1092,8 +1107,14 @@ public class ShortcutService extends IShortcutService.Stub {
                }

                synchronized (mServiceLock) {
                    // Since we are not handling package deletion yet, or any single package
                    // changes, just clean the directory and rewrite all the ShortcutPackageItems.
                    final File root = injectUserDataPath(userId);
                    FileUtils.deleteContents(new File(root, DIRECTORY_PACKAGES));
                    FileUtils.deleteContents(new File(root, DIRECTORY_LAUNCHERS));
                    os = file.startWrite();
                    saveUserInternalLocked(userId, os, /* forBackup= */ false);
                    getUserShortcutsLocked(userId).scheduleSaveAllLaunchersAndPackages();
                }

                injectFinishWrite(file, os);
@@ -1109,8 +1130,9 @@ public class ShortcutService extends IShortcutService.Stub {
        getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger);
    }

    @VisibleForTesting
    @GuardedBy("mServiceLock")
    private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
    protected void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
            boolean forBackup) throws IOException, XmlPullParserException {

        // Write to XML
@@ -1138,8 +1160,9 @@ public class ShortcutService extends IShortcutService.Stub {
        Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
    }

    @VisibleForTesting
    @Nullable
    private ShortcutUser loadUserLocked(@UserIdInt int userId) {
    protected ShortcutUser injectLoadUserLocked(@UserIdInt int userId) {
        try (ResilientAtomicFile file = getUserFile(userId)) {
            FileInputStream in = null;
            try {
@@ -1157,12 +1180,13 @@ public class ShortcutService extends IShortcutService.Stub {
            } catch (Exception e) {
                // Remove corrupted file and retry.
                file.failRead(in, e);
                return loadUserLocked(userId);
                return injectLoadUserLocked(userId);
            }
        }
    }

    private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
    @VisibleForTesting
    protected ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
            boolean fromBackup) throws XmlPullParserException, IOException,
            InvalidFileFormatException {

@@ -1240,9 +1264,9 @@ public class ShortcutService extends IShortcutService.Stub {
            for (int i = dirtyUserIds.size() - 1; i >= 0; i--) {
                final int userId = dirtyUserIds.get(i);
                if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
                    saveBaseState();
                    injectSaveBaseState();
                } else {
                    saveUser(userId);
                    injectSaveUser(userId);
                }
            }
        } catch (Exception e) {
@@ -1349,7 +1373,7 @@ public class ShortcutService extends IShortcutService.Stub {

        ShortcutUser userPackages = mUsers.get(userId);
        if (userPackages == null) {
            userPackages = loadUserLocked(userId);
            userPackages = injectLoadUserLocked(userId);
            if (userPackages == null) {
                userPackages = new ShortcutUser(this, userId);
            }
@@ -1430,8 +1454,9 @@ public class ShortcutService extends IShortcutService.Stub {
     * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
     * saves are going on.
     */
    @VisibleForTesting
    @GuardedBy("mServiceLock")
    private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) {
    void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) {
        if (DEBUG) {
            Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
        }
@@ -2755,7 +2780,7 @@ public class ShortcutService extends IShortcutService.Stub {
            getPackageShortcutsLocked(packageName, userId)
                    .resetRateLimitingForCommandLineNoSaving();
        }
        saveUser(userId);
        injectSaveUser(userId);
    }

    // We override this method in unit tests to do a simpler check.
@@ -4407,7 +4432,7 @@ public class ShortcutService extends IShortcutService.Stub {
                pw.println();
            });
        }
        saveUser(userId);
        injectSaveUser(userId);
    }

    // === Dump ===
+25 −29
Original line number Diff line number Diff line
@@ -21,16 +21,12 @@ import android.annotation.UserIdInt;
import android.content.pm.ShortcutManager;
import android.content.pm.UserPackage;
import android.metrics.LogMaker;
import android.os.Binder;
import android.os.FileUtils;
import android.os.UserHandle;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -49,8 +45,6 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

@@ -63,7 +57,7 @@ class ShortcutUser {
    private static final String TAG = ShortcutService.TAG;

    static final String DIRECTORY_PACKAGES = "packages";
    static final String DIRECTORY_LUANCHERS = "launchers";
    static final String DIRECTORY_LAUNCHERS = "launchers";

    static final String TAG_ROOT = "user";
    private static final String TAG_LAUNCHER = "launcher";
@@ -322,41 +316,43 @@ class ShortcutUser {
                    mService.injectBuildFingerprint());
        }

        if (!forBackup) {
            // Since we are not handling package deletion yet, or any single package changes, just
            // clean the directory and rewrite all the ShortcutPackageItems.
            final File root = mService.injectUserDataPath(mUserId);
            FileUtils.deleteContents(new File(root, DIRECTORY_PACKAGES));
            FileUtils.deleteContents(new File(root, DIRECTORY_LUANCHERS));
        }
        // Can't use forEachPackageItem due to the checked exceptions.
        if (forBackup) {
            int size = mLaunchers.size();
            for (int i = 0; i < size; i++) {
                saveShortcutPackageItem(out, mLaunchers.valueAt(i));
            }
            size = mPackages.size();
            for (int i = 0; i < size; i++) {
                saveShortcutPackageItem(out, mPackages.valueAt(i));
            }
        }

        out.endTag(null, TAG_ROOT);
    }

    void scheduleSaveAllLaunchersAndPackages() {
        {
            final int size = mLaunchers.size();
            for (int i = 0; i < size; i++) {
                saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
                mLaunchers.valueAt(i).scheduleSave();
            }
        }
        {
            final int size = mPackages.size();
            for (int i = 0; i < size; i++) {
                saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
                mPackages.valueAt(i).scheduleSave();
            }
        }

        out.endTag(null, TAG_ROOT);
    }

    private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi,
            boolean forBackup) throws IOException, XmlPullParserException {
        if (forBackup) {
    private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi)
            throws IOException, XmlPullParserException {
        if (spi.getPackageUserId() != spi.getOwnerUserId()) {
            return; // Don't save cross-user information.
        }
        spi.waitForBitmapSaves();
            spi.saveToXml(out, forBackup);
        } else {
            spi.scheduleSave();
        }
        spi.saveToXml(out, true /* forBackup */);
    }

    public static ShortcutUser loadFromXml(ShortcutService s, TypedXmlPullParser parser, int userId,
@@ -429,7 +425,7 @@ class ShortcutUser {
                }
            });

            forMainFilesIn(new File(root, DIRECTORY_LUANCHERS), (File f) -> {
            forMainFilesIn(new File(root, DIRECTORY_LAUNCHERS), (File f) -> {
                final ShortcutLauncher sl =
                        ShortcutLauncher.loadFromFile(f, ret, userId, fromBackup);
                if (sl != null) {
+58 −1
Original line number Diff line number Diff line
@@ -95,8 +95,8 @@ import android.test.mock.MockContext;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;

import com.android.internal.infra.AndroidFuture;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.LauncherAppsService.LauncherAppsImpl;
@@ -110,6 +110,7 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -149,6 +150,9 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
    protected static final String MAIN_ACTIVITY_CLASS = "MainActivity";
    protected static final String PIN_CONFIRM_ACTIVITY_CLASS = "PinConfirmActivity";

    private byte[] mBaseState;
    protected final SparseArray<byte[]> mUserStates = new SparseArray<>();

    // public for mockito
    public class BaseContext extends MockContext {
        @Override
@@ -287,6 +291,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
        final ServiceContext mContext;
        IUidObserver mUidObserver;


        public ShortcutServiceTestable(ServiceContext context, Looper looper) {
            super(context, looper, /* onyForPackageManagerApis */ false);
            mContext = context;
@@ -567,6 +572,58 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
            // During tests, WTF is fatal.
            fail(message + "  exception: " + th + "\n" + Log.getStackTraceString(th));
        }

        @Override
        void injectSaveBaseState() {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                saveBaseStateAsXml(baos);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            mBaseState = baos.toByteArray();
        }

        @Override
        protected void injectLoadBaseState() {
            if (mBaseState == null) {
                return;
            }
            ByteArrayInputStream bais = new ByteArrayInputStream(mBaseState);
            try {
                loadBaseStateAsXml(bais);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        protected void injectSaveUser(@UserIdInt int userId) {
            synchronized (mServiceLock) {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try {
                    saveUserInternalLocked(userId, baos, /* forBackup= */ false);
                    cleanupDanglingBitmapDirectoriesLocked(userId);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                mUserStates.put(userId, baos.toByteArray());
            }
        }

        @Override
        protected ShortcutUser injectLoadUserLocked(@UserIdInt int userId) {
            final byte[] userState = mUserStates.get(userId);
            if (userState == null) {
                return null;
            }
            ByteArrayInputStream bais = new ByteArrayInputStream(userState);
            try {
                return loadUserInternal(userId, bais, /* forBackup= */ false);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /** ShortcutManager with injection override methods. */
+1 −1
Original line number Diff line number Diff line
@@ -201,7 +201,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
        mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50;
        assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL);

        mService.saveBaseState();
        mService.injectSaveBaseState();

        dumpBaseStateFile();

Loading