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