Merge "Test behavior when declaring duplicate permissions"
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
index 1754dcd..490bb18 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -16,11 +16,14 @@
 
 package android.appsecurity.cts;
 
+import static android.appsecurity.cts.Utils.waitForBootCompleted;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
+
 import com.android.ddmlib.Log;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
@@ -78,6 +81,11 @@
     private static final String PERMISSION_DIFF_CERT_PKG =
         "com.android.cts.usespermissiondiffcertapp";
 
+    private static final String DUPLICATE_DECLARE_PERMISSION_APK =
+            "CtsDuplicatePermissionDeclareApp.apk";
+    private static final String DUPLICATE_DECLARE_PERMISSION_PKG =
+            "com.android.cts.duplicatepermissiondeclareapp";
+
     private static final String LOG_TAG = "AppSecurityTests";
 
     @Before
@@ -265,6 +273,27 @@
     }
 
     /**
+     * Test what happens if an app tried to take a permission away from another
+     */
+    @Test
+    public void rebootWithDuplicatePermission() throws Exception {
+        try {
+            new InstallMultiple(false).addApk(DECLARE_PERMISSION_APK).run();
+            new InstallMultiple(false).addApk(DUPLICATE_DECLARE_PERMISSION_APK).run();
+
+            runDeviceTests(DUPLICATE_DECLARE_PERMISSION_PKG, null);
+
+            // make sure behavior is preserved after reboot
+            getDevice().reboot();
+            waitForBootCompleted(getDevice());
+            runDeviceTests(DUPLICATE_DECLARE_PERMISSION_PKG, null);
+        } finally {
+            getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
+            getDevice().uninstallPackage(DUPLICATE_DECLARE_PERMISSION_PKG);
+        }
+    }
+
+    /**
      * Tests that an arbitrary file cannot be installed using the 'cmd' command.
      */
     @Test
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
index 6c598ec..6cdc9fd 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
@@ -16,13 +16,13 @@
 
 package android.appsecurity.cts;
 
+import static android.appsecurity.cts.Utils.waitForBootCompleted;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.RequiresDevice;
 
-import com.android.ddmlib.AdbCommandRejectedException;
-import com.android.ddmlib.CollectingOutputReceiver;
 import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -173,7 +173,7 @@
             } else {
                 getDevice().rebootUntilOnline();
             }
-            waitForBootCompleted();
+            waitForBootCompleted(getDevice());
 
             if (doTest) {
                 if (MODE_NONE.equals(mode)) {
@@ -215,21 +215,6 @@
         return getDevice().executeShellCommand("sm get-fbe-mode").trim();
     }
 
-    private boolean isBootCompleted() throws Exception {
-        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
-        try {
-            getDevice().getIDevice().executeShellCommand("getprop sys.boot_completed", receiver);
-        } catch (AdbCommandRejectedException e) {
-            // do nothing: device might be temporarily disconnected
-            Log.d(TAG, "Ignored AdbCommandRejectedException while `getprop sys.boot_completed`");
-        }
-        String output = receiver.getOutput();
-        if (output != null) {
-            output = output.trim();
-        }
-        return "1".equals(output);
-    }
-
     private boolean isSupportedDevice() throws Exception {
         return getDevice().hasFeature(FEATURE_DEVICE_ADMIN);
     }
@@ -238,21 +223,6 @@
         return getDevice().hasFeature(FEATURE_AUTOMOTIVE);
     }
 
-    private void waitForBootCompleted() throws Exception {
-        for (int i = 0; i < 45; i++) {
-            if (isBootCompleted()) {
-                Log.d(TAG, "Yay, system is ready!");
-                // or is it really ready?
-                // guard against potential USB mode switch weirdness at boot
-                Thread.sleep(10 * 1000);
-                return;
-            }
-            Log.d(TAG, "Waiting for system ready...");
-            Thread.sleep(1000);
-        }
-        throw new AssertionError("System failed to become ready!");
-    }
-
     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
         public InstallMultiple() {
             super(getDevice(), getBuild(), getAbi());
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
index d5aa6e1..0b5594b 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
@@ -16,6 +16,9 @@
 
 package android.appsecurity.cts;
 
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.CollectingOutputReceiver;
+import com.android.ddmlib.Log;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -31,6 +34,8 @@
 import java.util.concurrent.TimeUnit;
 
 public class Utils {
+    private static final String LOG_TAG = Utils.class.getSimpleName();
+
     public static final int USER_SYSTEM = 0;
 
     public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
@@ -157,4 +162,35 @@
         }
         return users;
     }
+
+    public static void waitForBootCompleted(ITestDevice device) throws Exception {
+        for (int i = 0; i < 45; i++) {
+            if (isBootCompleted(device)) {
+                Log.d(LOG_TAG, "Yay, system is ready!");
+                // or is it really ready?
+                // guard against potential USB mode switch weirdness at boot
+                Thread.sleep(10 * 1000);
+                return;
+            }
+            Log.d(LOG_TAG, "Waiting for system ready...");
+            Thread.sleep(1000);
+        }
+        throw new AssertionError("System failed to become ready!");
+    }
+
+    private static boolean isBootCompleted(ITestDevice device) throws Exception {
+        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        try {
+            device.getIDevice().executeShellCommand("getprop sys.boot_completed", receiver);
+        } catch (AdbCommandRejectedException e) {
+            // do nothing: device might be temporarily disconnected
+            Log.d(LOG_TAG, "Ignored AdbCommandRejectedException while `getprop sys.boot_completed`");
+        }
+        String output = receiver.getOutput();
+        if (output != null) {
+            output = output.trim();
+        }
+        return "1".equals(output);
+    }
+
 }
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.mk b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.mk
new file mode 100644
index 0000000..ce49914
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.mk
@@ -0,0 +1,36 @@
+# 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_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_PACKAGE_NAME := CtsDuplicatePermissionDeclareApp
+
+# Use the same cert as the app that also defined the permission
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/AndroidManifest.xml
new file mode 100644
index 0000000..0c8b2d6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/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="com.android.cts.duplicatepermissiondeclareapp">
+
+    <permission android:name="com.android.cts.permissionNormal"/>
+    <uses-permission android:name="com.android.cts.permissionNormal" />
+
+    <permission-tree android:name="com.android.cts" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.duplicatepermissiondeclareapp" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/src/com/android/cts/duplicatepermissiondeclareapp/VerifyDuplicatePermissionTest.java b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/src/com/android/cts/duplicatepermissiondeclareapp/VerifyDuplicatePermissionTest.java
new file mode 100644
index 0000000..3584876
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/src/com/android/cts/duplicatepermissiondeclareapp/VerifyDuplicatePermissionTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.duplicatepermissiondeclareapp;
+
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VerifyDuplicatePermissionTest {
+    private final static String APP_THAT_WAS_INSTALLED_FIRST =
+            "com.android.cts.permissiondeclareapp";
+    private final static String APP_THAT_WAS_INSTALLED_AFTER =
+            "com.android.cts.duplicatepermissiondeclareapp";
+    private final static String PERM = "com.android.cts.permissionNormal";
+
+    private final static PackageManager sPm =
+            InstrumentationRegistry.getTargetContext().getPackageManager();
+
+    private PermissionInfo getDeclaredPermission(String pkg, String permission) throws Exception {
+        for (PermissionInfo permInfo : sPm.getPackageInfo(pkg, GET_PERMISSIONS).permissions) {
+            if (permInfo.name.equals(permission)) {
+                return permInfo;
+            }
+        }
+
+        return null;
+    }
+
+    @Test
+    public void verifyDuplicatePermission() throws Exception {
+        // The other app was first. The second app could not steal the permission. Hence the
+        // permission belongs to the first app.
+        assertEquals(APP_THAT_WAS_INSTALLED_FIRST, sPm.getPermissionInfo(PERM, 0).packageName);
+
+        // The first app declared the permission
+        assertEquals(APP_THAT_WAS_INSTALLED_FIRST,
+                getDeclaredPermission(APP_THAT_WAS_INSTALLED_FIRST, PERM).packageName);
+
+        // The second app declared the permission too, but the permission is owned by first app.
+        // Hence we end up with a permission info that is different from the one read from the
+        // system.
+        assertEquals(APP_THAT_WAS_INSTALLED_AFTER,
+                getDeclaredPermission(APP_THAT_WAS_INSTALLED_AFTER, PERM).packageName);
+    }
+}