Merge "Verify staged session remain unchanged on system server reboot" into rvc-dev am: 9cf5156fed

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11838108

Change-Id: Iac224a8b8d586c20deffdfaf93e2d541bc80cefd
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
new file mode 100644
index 0000000..c3fdd69
--- /dev/null
+++ b/tests/StagedInstallTest/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "StagedInstallInternalTestApp",
+    manifest: "app/AndroidManifest.xml",
+    srcs: ["app/src/**/*.java"],
+    static_libs: ["androidx.test.rules", "cts-install-lib"],
+    test_suites: ["general-tests"],
+}
+
+java_test_host {
+    name: "StagedInstallInternalTest",
+    srcs: ["src/**/*.java"],
+    libs: ["tradefed"],
+    static_libs: ["testng", "compatibility-tradefed"],
+    test_suites: ["general-tests"],
+    test_config: "StagedInstallInternalTest.xml",
+}
+
diff --git a/tests/StagedInstallTest/StagedInstallInternalTest.xml b/tests/StagedInstallTest/StagedInstallInternalTest.xml
new file mode 100644
index 0000000..1b8fa67
--- /dev/null
+++ b/tests/StagedInstallTest/StagedInstallInternalTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs the internal staged install tests">
+    <option name="test-suite-tag" value="StagedInstallTest" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="StagedInstallInternalTestApp.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class"
+                value="com.android.tests.stagedinstallinternal.host.StagedInstallInternalTest" />
+    </test>
+</configuration>
diff --git a/tests/StagedInstallTest/TEST_MAPPING b/tests/StagedInstallTest/TEST_MAPPING
new file mode 100644
index 0000000..5a7a5a7
--- /dev/null
+++ b/tests/StagedInstallTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "StagedInstallInternalTest"
+    }
+  ]
+}
diff --git a/tests/StagedInstallTest/app/AndroidManifest.xml b/tests/StagedInstallTest/app/AndroidManifest.xml
new file mode 100644
index 0000000..a678f1ec
--- /dev/null
+++ b/tests/StagedInstallTest/app/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.tests.stagedinstallinternal" >
+
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <application>
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+                  android:exported="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.tests.stagedinstallinternal"
+                     android:label="StagedInstallInternal Test"/>
+
+</manifest>
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
new file mode 100644
index 0000000..02597d5
--- /dev/null
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.stagedinstallinternal;
+
+import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.Manifest;
+import android.content.pm.PackageInstaller;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.function.Consumer;
+
+@RunWith(JUnit4.class)
+public class StagedInstallInternalTest {
+
+    private static final String TAG = StagedInstallInternalTest.class.getSimpleName();
+
+    private File mTestStateFile = new File(
+            InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
+            "stagedinstall_state");
+
+    /**
+     * Adopts common shell permissions needed for staged install tests.
+     */
+    @Before
+    public void adoptShellPermissions() {
+        InstallUtils.adoptShellPermissionIdentity(
+                Manifest.permission.INSTALL_PACKAGES,
+                Manifest.permission.DELETE_PACKAGES
+        );
+    }
+
+    /**
+     * Drops shell permissions needed for staged install tests.
+     */
+    @After
+    public void dropShellPermissions() {
+        InstallUtils.dropShellPermissionIdentity();
+    }
+
+    // This is marked as @Test to take advantage of @Before/@After methods of this class. Actual
+    // purpose of this method to be called before and after each test case of
+    // com.android.test.stagedinstall.host.StagedInstallTest to reduce tests flakiness.
+    @Test
+    public void cleanUp() throws Exception {
+        Files.deleteIfExists(mTestStateFile.toPath());
+    }
+
+    @Test
+    public void testSystemServerRestartDoesNotAffectStagedSessions_Commit() throws Exception {
+        int sessionId = Install.single(TestApp.A1).setStaged().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertSessionReady(sessionId);
+        storeSessionId(sessionId);
+    }
+
+    @Test
+    public void testSystemServerRestartDoesNotAffectStagedSessions_Verify() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        int sessionId = retrieveLastSessionId();
+        assertSessionReady(sessionId);
+    }
+
+    private static void assertSessionReady(int sessionId) {
+        assertSessionState(sessionId,
+                (session) -> assertThat(session.isStagedSessionReady()).isTrue());
+    }
+
+    private static void assertSessionState(
+            int sessionId, Consumer<PackageInstaller.SessionInfo> assertion) {
+        PackageInstaller packageInstaller = getPackageInstaller();
+
+        List<PackageInstaller.SessionInfo> sessions = packageInstaller.getStagedSessions();
+        boolean found = false;
+        for (PackageInstaller.SessionInfo session : sessions) {
+            if (session.getSessionId() == sessionId) {
+                assertion.accept(session);
+                found = true;
+            }
+        }
+        assertWithMessage("Expecting to find session in getStagedSession()")
+                .that(found).isTrue();
+
+        // Test also that getSessionInfo correctly returns the session.
+        PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
+        assertion.accept(sessionInfo);
+    }
+
+    private void storeSessionId(int sessionId) throws Exception {
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(mTestStateFile))) {
+            writer.write("" + sessionId);
+        }
+    }
+
+    private int retrieveLastSessionId() throws Exception {
+        try (BufferedReader reader = new BufferedReader(new FileReader(mTestStateFile))) {
+            return Integer.parseInt(reader.readLine());
+        }
+    }
+}
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
new file mode 100644
index 0000000..9b432f7
--- /dev/null
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.stagedinstallinternal.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.ProcessInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class StagedInstallInternalTest extends BaseHostJUnit4Test {
+
+    private static final String TAG = StagedInstallInternalTest.class.getSimpleName();
+    private static final long SYSTEM_SERVER_TIMEOUT_MS = 60 * 1000;
+    private boolean mWasRoot = false;
+
+    /**
+     * Runs the given phase of a test by calling into the device.
+     * Throws an exception if the test phase fails.
+     * <p>
+     * For example, <code>runPhase("testApkOnlyEnableRollback");</code>
+     */
+    private void runPhase(String phase) throws Exception {
+        assertTrue(runDeviceTests("com.android.tests.stagedinstallinternal",
+                "com.android.tests.stagedinstallinternal.StagedInstallInternalTest",
+                phase));
+    }
+
+    // We do not assert the success of cleanup phase since it might fail due to flaky reasons.
+    private void cleanUp() throws Exception {
+        try {
+            runDeviceTests("com.android.tests.stagedinstallinternal",
+                    "com.android.tests.stagedinstallinternal.StagedInstallInternalTest",
+                    "cleanUp");
+        } catch (AssertionError e) {
+            Log.e(TAG, e);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mWasRoot = getDevice().isAdbRoot();
+        if (!mWasRoot) {
+            getDevice().enableAdbRoot();
+        }
+        cleanUp();
+        // Abandon all staged sessions
+        getDevice().executeShellCommand("pm install-abandon $(pm get-stagedsessions --only-ready "
+                + "--only-parent --only-sessionid)");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mWasRoot) {
+            getDevice().disableAdbRoot();
+        }
+        cleanUp();
+    }
+
+    @Test
+    public void testSystemServerRestartDoesNotAffectStagedSessions() throws Exception {
+        runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Commit");
+        restartSystemServer();
+        runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Verify");
+    }
+
+    private void restartSystemServer() throws Exception {
+        // Restart the system server
+        long oldStartTime = getDevice().getProcessByName("system_server").getStartTime();
+        assertThat(getDevice().executeShellCommand("am restart")).contains("Restart the system");
+
+        // Wait for new system server process to start
+        long start = System.currentTimeMillis();
+        long newStartTime = oldStartTime;
+        while (System.currentTimeMillis() < start + SYSTEM_SERVER_TIMEOUT_MS) {
+            ProcessInfo newPs = getDevice().getProcessByName("system_server");
+            if (newPs != null) {
+                newStartTime = newPs.getStartTime();
+                if (newStartTime != oldStartTime) {
+                    break;
+                }
+            }
+            Thread.sleep(500);
+        }
+        assertThat(newStartTime).isNotEqualTo(oldStartTime);
+        getDevice().waitForDeviceAvailable();
+    }
+}