Test that uninstall activity hides overlays

Bug: 171221302
Test: cts-tradefed run cts-dev -m CtsPackageUninstallTestCases
Merged-In: I112fc124d5122f30b3998f947cb20217c4270804
Change-Id: I112fc124d5122f30b3998f947cb20217c4270804
diff --git a/tests/tests/packageinstaller/uninstall/Android.mk b/tests/tests/packageinstaller/uninstall/Android.mk
new file mode 100644
index 0000000..3b463ba
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2018 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PACKAGE_NAME := CtsPackageUninstallTestCases
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator \
+    android-support-test \
+    compatibility-device-util
+    
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := res
+
+LOCAL_SDK_VERSION := current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/packageinstaller/uninstall/AndroidManifest.xml b/tests/tests/packageinstaller/uninstall/AndroidManifest.xml
new file mode 100644
index 0000000..28c2b0d
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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="android.packageinstaller.uninstall.cts" >
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
+
+    <application android:label="Cts Package Uninstaller Tests">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.packageinstaller.uninstall.cts"
+                     android:label="Package Uninstaller Tests"/>
+
+</manifest>
diff --git a/tests/tests/packageinstaller/uninstall/AndroidTest.xml b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
new file mode 100644
index 0000000..424a981
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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="Config for CTS Packageinstaller Uninstall test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsPackageUninstallTestCases.apk" />
+        <option name="test-file-name" value="CtsEmptyTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.packageinstaller.uninstall.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/packageinstaller/uninstall/res/layout/overlay_activity.xml b/tests/tests/packageinstaller/uninstall/res/layout/overlay_activity.xml
new file mode 100644
index 0000000..869bf14
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/res/layout/overlay_activity.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:padding="8dp"
+              android:gravity="center">
+
+    <TextView android:id="@+id/overlay_description"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:textColor="@android:color/black"
+              android:background="@android:color/white"
+              android:text="This is an overlay" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/AppOpsUtils.java b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/AppOpsUtils.java
new file mode 100644
index 0000000..365f145
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/AppOpsUtils.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 Google Inc.
+ *
+ * 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 android.packageinstaller.uninstall.cts;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+
+import android.app.AppOpsManager;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+
+/**
+ * Utilities for controlling App Ops settings, and testing whether ops are logged.
+ */
+public class AppOpsUtils {
+
+    /**
+     * Resets a package's app ops configuration to the device default. See AppOpsManager for the
+     * default op settings.
+     *
+     * <p>
+     * It's recommended to call this in setUp() and tearDown() of your test so the test starts and
+     * ends with a reproducible default state, and so doesn't affect other tests.
+     *
+     * <p>
+     * Some app ops are configured to be non-resettable, which means that the state of these will
+     * not be reset even when calling this method.
+     */
+    public static String reset(String packageName) throws IOException {
+        return runCommand("appops reset " + packageName);
+    }
+
+    /**
+     * Sets the app op mode (e.g. allowed, denied) for a single package and operation.
+     */
+    public static String setOpMode(String packageName, String opStr, int mode)
+            throws IOException {
+        String modeStr;
+        switch (mode) {
+            case MODE_ALLOWED:
+                modeStr = "allow";
+                break;
+            case MODE_ERRORED:
+                modeStr = "deny";
+                break;
+            case MODE_IGNORED:
+                modeStr = "ignore";
+                break;
+            case MODE_DEFAULT:
+                modeStr = "default";
+                break;
+            default:
+                throw new IllegalArgumentException("Unexpected app op type");
+        }
+        String command = "appops set " + packageName + " " + opStr + " " + modeStr;
+        return runCommand(command);
+    }
+
+    /**
+     * Get the app op mode (e.g. MODE_ALLOWED, MODE_DEFAULT) for a single package and operation.
+     */
+    public static int getOpMode(String packageName, String opStr)
+            throws IOException {
+        String opState = getOpState(packageName, opStr);
+        if (opState.contains(" allow")) {
+            return MODE_ALLOWED;
+        } else if (opState.contains(" deny")) {
+            return MODE_ERRORED;
+        } else if (opState.contains(" ignore")) {
+            return MODE_IGNORED;
+        } else if (opState.contains(" default")) {
+            return MODE_DEFAULT;
+        } else {
+            throw new IllegalStateException("Unexpected app op mode returned " + opState);
+        }
+    }
+
+    /**
+     * Returns whether an allowed operation has been logged by the AppOpsManager for a
+     * package. Operations are noted when the app attempts to perform them and calls e.g.
+     * {@link AppOpsManager#noteOperation}.
+     *
+     * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
+     */
+    public static boolean allowedOperationLogged(String packageName, String opStr)
+            throws IOException {
+        return getOpState(packageName, opStr).contains(" time=");
+    }
+
+    /**
+     * Returns whether a rejected operation has been logged by the AppOpsManager for a
+     * package. Operations are noted when the app attempts to perform them and calls e.g.
+     * {@link AppOpsManager#noteOperation}.
+     *
+     * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
+     */
+    public static boolean rejectedOperationLogged(String packageName, String opStr)
+            throws IOException {
+        return getOpState(packageName, opStr).contains(" rejectTime=");
+    }
+
+    /**
+     * Returns the app op state for a package. Includes information on when the operation was last
+     * attempted to be performed by the package.
+     *
+     * Format: "SEND_SMS: allow; time=+23h12m54s980ms ago; rejectTime=+1h10m23s180ms"
+     */
+    private static String getOpState(String packageName, String opStr) throws IOException {
+        return runCommand("appops get " + packageName + " " + opStr);
+    }
+
+    private static String runCommand(String command) throws IOException {
+        return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+}
diff --git a/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallTest.java b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallTest.java
new file mode 100644
index 0000000..7259fa6
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 Google Inc.
+ *
+ * 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 android.packageinstaller.uninstall.cts;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.graphics.PixelFormat.TRANSLUCENT;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.SecurityTest;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+@RunWith(AndroidJUnit4.class)
+public class UninstallTest {
+    private static final String LOG_TAG = UninstallTest.class.getSimpleName();
+
+    private static final String TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts";
+
+    private static final long TIMEOUT_MS = 30000;
+
+    private Context mContext;
+    private UiDevice mUiDevice;
+
+    @Before
+    public void setup() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+
+        // Unblock UI
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        if (!mUiDevice.isScreenOn()) {
+            mUiDevice.wakeUp();
+        }
+        mUiDevice.executeShellCommand("wm dismiss-keyguard");
+        AppOpsUtils.reset(mContext.getPackageName());
+    }
+
+    private void startUninstall() {
+        Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
+        intent.setData(Uri.parse("package:" + TEST_APK_PACKAGE_NAME));
+        intent.addFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
+    @SecurityTest
+    @Test
+    public void overlaysAreSuppressedWhenConfirmingUninstall() throws Exception {
+        AppOpsUtils.setOpMode(mContext.getPackageName(), "SYSTEM_ALERT_WINDOW", MODE_ALLOWED);
+
+        WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+        LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT,
+                TYPE_APPLICATION_OVERLAY, 0, TRANSLUCENT);
+        layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL;
+
+        View[] overlay = new View[1];
+        new Handler(Looper.getMainLooper()).post(() -> {
+            overlay[0] = LayoutInflater.from(mContext).inflate(R.layout.overlay_activity,
+                    null);
+            windowManager.addView(overlay[0], layoutParams);
+        });
+
+        try {
+            mUiDevice.wait(Until.findObject(By.res(mContext.getPackageName(),
+                    "overlay_description")), TIMEOUT_MS);
+
+            startUninstall();
+
+            long start = System.currentTimeMillis();
+            while (System.currentTimeMillis() - start < TIMEOUT_MS) {
+                try {
+                    assertNull(mUiDevice.findObject(By.res(mContext.getPackageName(),
+                            "overlay_description")));
+                    return;
+                } catch (Throwable e) {
+                    Thread.sleep(100);
+                }
+            }
+
+            fail();
+        } finally {
+            windowManager.removeView(overlay[0]);
+        }
+    }
+}