Add tests for uninstalling while task is pinned

If the device is in locked task mode then package can't be uninstalled
because this might force the device out of the locked mode.

1. Test that the app can't be uninstalled if the app requests to be
uninstalled.
2. Test that an app can't uninstall another one using the
packageinstaller apis.
3. Test that an app can't be uninstalled using shell commands.

Test: These tests with and without the fameworks patch
Bug: 135604684
Change-Id: Ia6143a32770a87ec7ecffef0286062422aa0acfc
Merged-In: Ia6143a32770a87ec7ecffef0286062422aa0acfc
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
new file mode 100644
index 0000000..a5e2fd3
--- /dev/null
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
@@ -0,0 +1,34 @@
+// 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: "CtsSelfUninstallingTestApp",
+    defaults: ["cts_defaults"],
+
+    sdk_version: "current",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "androidx.core_core",
+    ],
+
+    // tag this module as a cts test artifact
+    test_suites: [
+        "arcts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/AndroidManifest.xml b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..87dc715
--- /dev/null
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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="android.packageinstaller.selfuninstalling.cts" >
+
+    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
+
+    <application android:label="Self Uninstalling Test App">
+        <activity android:name=".SelfUninstallActivity"
+                  android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/res/layout/self_uninstalling_activity.xml b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/res/layout/self_uninstalling_activity.xml
new file mode 100644
index 0000000..ac0fb40
--- /dev/null
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/res/layout/self_uninstalling_activity.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" >
+    <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Pin me!" />
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/src/android/packageinstaller/selfuninstalling/cts/SelfUninstallActivity.java b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/src/android/packageinstaller/selfuninstalling/cts/SelfUninstallActivity.java
new file mode 100644
index 0000000..df5b1d4
--- /dev/null
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/src/android/packageinstaller/selfuninstalling/cts/SelfUninstallActivity.java
@@ -0,0 +1,33 @@
+package android.packageinstaller.selfuninstalling.cts;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.Nullable;
+
+public class SelfUninstallActivity extends Activity {
+
+    private static final String ACTION_SELF_UNINSTALL =
+            "android.packageinstaller.selfuninstalling.cts.action.SELF_UNINSTALL";
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.self_uninstalling_activity);
+        registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Intent i = new Intent(Intent.ACTION_UNINSTALL_PACKAGE).setData(
+                        Uri.fromParts("package", getPackageName(), null));
+                startActivity(i);
+            }
+        }, new IntentFilter(ACTION_SELF_UNINSTALL));
+    }
+}
diff --git a/tests/tests/packageinstaller/uninstall/Android.bp b/tests/tests/packageinstaller/uninstall/Android.bp
index 53ecc9d..16e5de4 100644
--- a/tests/tests/packageinstaller/uninstall/Android.bp
+++ b/tests/tests/packageinstaller/uninstall/Android.bp
@@ -20,6 +20,7 @@
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "platform-test-annotations",
+        "cts-wm-util",
     ],
     srcs: ["src/**/*.java"],
     sdk_version: "test_current",
diff --git a/tests/tests/packageinstaller/uninstall/AndroidTest.xml b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
index 9fc0a88..dd98214 100644
--- a/tests/tests/packageinstaller/uninstall/AndroidTest.xml
+++ b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
@@ -31,4 +31,14 @@
         <option name="package" value="android.packageinstaller.uninstall.cts" />
         <option name="runtime-hint" value="1m" />
     </test>
+    <!-- Create place to store apks -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/uninstall" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+    </target_preparer>
+
+    <!-- Load additional APKs onto device -->
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="CtsSelfUninstallingTestApp.apk->/data/local/tmp/cts/uninstall/CtsSelfUninstallingTestApp.apk" />
+    </target_preparer>
 </configuration>
diff --git a/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallPinnedTest.java b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallPinnedTest.java
new file mode 100644
index 0000000..84c2696
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallPinnedTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2020 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.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.server.wm.WindowManagerStateHelper;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class UninstallPinnedTest {
+
+    private static final String APK =
+            "/data/local/tmp/cts/uninstall/CtsSelfUninstallingTestApp.apk";
+    private static final String TEST_PKG_NAME = "android.packageinstaller.selfuninstalling.cts";
+    private static final String TEST_ACTIVITY_NAME = TEST_PKG_NAME + ".SelfUninstallActivity";
+    private static final String ACTION_SELF_UNINSTALL =
+            "android.packageinstaller.selfuninstalling.cts.action.SELF_UNINSTALL";
+    private static final ComponentName COMPONENT = new ComponentName(TEST_PKG_NAME, TEST_ACTIVITY_NAME);
+    public static final String CALLBACK_ACTION =
+            "android.packageinstaller.uninstall.cts.action.UNINSTALL_PINNED_CALLBACK";
+
+    private WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+    private Context mContext;
+    private UiDevice mUiDevice;
+    private ActivityTaskManager mActivityTaskManager;
+
+    @Before
+    public void setup() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
+
+        // Unblock UI
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        if (!mUiDevice.isScreenOn()) {
+            mUiDevice.wakeUp();
+        }
+        mUiDevice.executeShellCommand("wm dismiss-keyguard");
+        AppOpsUtils.reset(mContext.getPackageName());
+
+        runShellCommand("pm install -r --force-queryable " + APK);
+
+        Intent i = new Intent()
+                .setComponent(COMPONENT)
+                .addFlags(FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(i);
+
+        pinActivity(COMPONENT);
+    }
+
+    @Test
+    public void testAppCantUninstallItself() throws Exception {
+        mUiDevice.waitForIdle();
+        eventually(() -> {
+            mContext.sendBroadcast(new Intent(ACTION_SELF_UNINSTALL));
+            waitFindObject(By.text("OK")).click();
+        }, 60000);
+
+        mUiDevice.waitForIdle();
+
+        Thread.sleep(5000);
+
+        assertTrue("Package was uninstalled.", isInstalled());
+    }
+
+    @Test
+    public void testCantUninstallAppDirectly() {
+        CompletableFuture<Integer> statusFuture = new CompletableFuture<>();
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                statusFuture.complete(
+                        intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MAX_VALUE));
+            }
+        }, new IntentFilter(CALLBACK_ACTION));
+
+        runWithShellPermissionIdentity(() -> {
+            mContext.getPackageManager().getPackageInstaller().uninstall(TEST_PKG_NAME,
+                    PendingIntent.getBroadcast(mContext, 1,
+                            new Intent(CALLBACK_ACTION),
+                            0).getIntentSender());
+        });
+
+        int status = statusFuture.join();
+        assertEquals("Wrong code received", PackageInstaller.STATUS_FAILURE_BLOCKED, status);
+        assertTrue("Package was uninstalled.", isInstalled());
+    }
+
+    @Test
+    public void testCantUninstallWithShell() throws Exception {
+        mUiDevice.executeShellCommand("pm uninstall " + TEST_PKG_NAME);
+        assertTrue("Package was uninstalled.", isInstalled());
+    }
+
+    @After
+    public void unpinAndUninstall() throws IOException {
+        runWithShellPermissionIdentity(() -> mActivityTaskManager.stopSystemLockTaskMode());
+        mUiDevice.executeShellCommand("pm uninstall " + TEST_PKG_NAME);
+    }
+
+    private void pinActivity(ComponentName component) {
+        mWmState.computeState();
+
+        int stackId = mWmState.getStackIdByActivity(component);
+
+        runWithShellPermissionIdentity(() -> {
+            mActivityTaskManager.startSystemLockTaskMode(
+                    stackId);
+        });
+    }
+
+    private boolean isInstalled() {
+        try {
+            mContext.getPackageManager().getPackageInfo(TEST_PKG_NAME, 0);
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+}