Test backup + restore of permissions
- foreground/background permissions
- permission groups
- pre-M apps
- user-set/user-fixed/review-required flag
- delayed permission restore
Test: atest CtsBackupTestCases:android.backup.cts.PermissionTest
Change-Id: Ic925625ecc1679e9041650ad060e40cf11b38744
diff --git a/tests/backup/AndroidTest.xml b/tests/backup/AndroidTest.xml
index 635bfdc..a7081d6 100644
--- a/tests/backup/AndroidTest.xml
+++ b/tests/backup/AndroidTest.xml
@@ -26,6 +26,17 @@
<option name="test-file-name" value="CtsKeyValueBackupApp.apk" />
<option name="test-file-name" value="CtsBackupTestCases.apk" />
<option name="test-file-name" value="CtsPermissionBackupApp.apk" />
+ <option name="test-file-name" value="CtsPermissionBackupApp22.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/backup" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <!-- Also copy the test apks to the device -->
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsPermissionBackupApp.apk->/data/local/tmp/cts/backup/CtsPermissionBackupApp.apk" />
+ <option name="push" value="CtsPermissionBackupApp22.apk->/data/local/tmp/cts/backup/CtsPermissionBackupApp22.apk" />
</target_preparer>
<target_preparer class="android.cts.backup.BackupPreparer">
<option name="enable-backup-if-needed" value="true" />
diff --git a/tests/backup/app/permission/AndroidManifest.xml b/tests/backup/app/permission/AndroidManifest.xml
index 8dd735a..7dd4701 100644
--- a/tests/backup/app/permission/AndroidManifest.xml
+++ b/tests/backup/app/permission/AndroidManifest.xml
@@ -18,8 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.backup.permission" >
- <uses-permission android:name="android.permission.READ_SMS"/>
- <uses-permission android:name="android.permission.SEND_SMS"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
diff --git a/tests/backup/app/permission22/Android.mk b/tests/backup/app/permission22/Android.mk
new file mode 100644
index 0000000..8790e19
--- /dev/null
+++ b/tests/backup/app/permission22/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2019 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 := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_PACKAGE_NAME := CtsPermissionBackupApp22
+LOCAL_SDK_VERSION := current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/backup/app/permission22/AndroidManifest.xml b/tests/backup/app/permission22/AndroidManifest.xml
new file mode 100644
index 0000000..d82d5bf
--- /dev/null
+++ b/tests/backup/app/permission22/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.backup.permission22" >
+
+ <uses-sdk android:targetSdkVersion="22" />
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.READ_CONTACTS"/>
+ <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+
+ <application android:label="Android Permission Backup CTS App (API 22)" />
+</manifest>
diff --git a/tests/backup/src/android/backup/cts/PermissionTest.java b/tests/backup/src/android/backup/cts/PermissionTest.java
index bf81def..7b64efb 100644
--- a/tests/backup/src/android/backup/cts/PermissionTest.java
+++ b/tests/backup/src/android/backup/cts/PermissionTest.java
@@ -16,31 +16,61 @@
package android.backup.cts;
-import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.Manifest.permission.WRITE_CONTACTS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.permissionToOp;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import android.Manifest;
-import android.app.Instrumentation;
-import android.content.pm.PackageManager;
+import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.app.AppOpsManager;
+import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeFull;
+import android.support.test.InstrumentationRegistry;
+
+import androidx.annotation.NonNull;
import com.android.compatibility.common.util.BackupUtils;
+import com.android.compatibility.common.util.ShellUtils;
import java.io.IOException;
import java.io.InputStream;
/**
* Verifies that restored permissions are the same with backup value.
+ *
+ * @see com.android.packageinstaller.permission.service.BackupHelper
*/
@AppModeFull
public class PermissionTest extends BaseBackupCtsTest {
- /** The name of the package of the app under test */
- private static final String APP_PACKAGE = "android.backup.permission";
+ /** The name of the package of the apps under test */
+ private static final String APP = "android.backup.permission";
+ private static final String APP22 = "android.backup.permission22";
+
+ /** The apk of the packages */
+ private static final String APK_PATH = "/data/local/tmp/cts/backup/";
+ private static final String APP_APK = APK_PATH + "CtsPermissionBackupApp.apk";
+ private static final String APP22_APK = APK_PATH + "CtsPermissionBackupApp22.apk";
/** The name of the package for backup */
private static final String ANDROID_PACKAGE = "android";
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private static final long TIMEOUT_MILLIS = 10000;
+
private BackupUtils mBackupUtils =
new BackupUtils() {
@Override
@@ -51,49 +81,328 @@
}
};
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ resetApp(APP);
+ resetApp(APP22);
+ }
+
/**
- * Test backup and restore of grant runtime permission.
- *
- * Test logic:
- * 1. Grant SEND_SMS and WRITE_CONTACTS permissions to APP_PACKAGE.
- * 2. Backup android package, revoke SEND_SMS and WRITE_CONTACTS permissions to APP_PACKAGE.
- * Then restore android package.
- * 3. Check restored SEND_SMS and WRITE_CONTACTS permissions.
- *
- * @see PackageManagerService#serializeRuntimePermissionGrantsLPr(XmlSerializer, int) and
- * PackageManagerService#processRestoredPermissionGrantsLPr(XmlPullParser, int)
+ * Test backup and restore of regular runtime permission.
*/
- public void testGrantRuntimePermission() throws Exception {
- grantRuntimePermission(Manifest.permission.SEND_SMS);
- grantRuntimePermission(Manifest.permission.WRITE_CONTACTS);
+ public void testGrantDeniedRuntimePermission() throws Exception {
+ grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
- revokeRuntimePermission(Manifest.permission.SEND_SMS);
- revokeRuntimePermission(Manifest.permission.WRITE_CONTACTS);
+ resetApp(APP);
mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
- assertEquals(PackageManager.PERMISSION_GRANTED,
- checkPermission(Manifest.permission.SEND_SMS));
- assertEquals(PackageManager.PERMISSION_GRANTED,
- checkPermission(Manifest.permission.WRITE_CONTACTS));
+ eventually(() -> {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION));
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS));
+ });
}
- private int checkPermission(String permission) {
- return getInstrumentation().getContext().getPackageManager().checkPermission(permission,
- APP_PACKAGE);
+ /**
+ * Test backup and restore of pre-M regular runtime permission.
+ */
+ public void testGrantDeniedRuntimePermission22() throws Exception {
+ setAppOp(APP22, READ_CONTACTS, MODE_IGNORED);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP22);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> {
+ assertEquals(MODE_IGNORED, getAppOp(APP22, READ_CONTACTS));
+ assertEquals(MODE_ALLOWED, getAppOp(APP22, ACCESS_FINE_LOCATION));
+ });
}
- private void grantRuntimePermission(String permission) {
- if (checkPermission(permission) != PackageManager.PERMISSION_GRANTED) {
- getInstrumentation().getUiAutomation().grantRuntimePermission(APP_PACKAGE, permission);
- assertEquals(PackageManager.PERMISSION_GRANTED, checkPermission(permission));
+ /**
+ * Test backup and restore of foreground runtime permission.
+ */
+ public void testNoTriStateRuntimePermission() throws Exception {
+ // Set a marker
+ grantRuntimePermission(APP, WRITE_CONTACTS);
+
+ // revoked is the default state. Hence mark the permissions as user set, so the permissions
+ // are even backed up
+ setFlag(APP, ACCESS_FINE_LOCATION, FLAG_PERMISSION_USER_SET);
+ setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> {
+ // Wait until marker is set
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, WRITE_CONTACTS));
+
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION));
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION));
+ assertEquals(MODE_IGNORED, getAppOp(APP, ACCESS_FINE_LOCATION));
+ });
+ }
+
+ /**
+ * Test backup and restore of foreground runtime permission.
+ */
+ public void testNoTriStateRuntimePermission22() throws Exception {
+ setAppOp(APP22, ACCESS_FINE_LOCATION, MODE_IGNORED);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP22);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> assertEquals(MODE_IGNORED, getAppOp(APP22, ACCESS_FINE_LOCATION)));
+ }
+
+ /**
+ * Test backup and restore of foreground runtime permission.
+ */
+ public void testGrantForegroundRuntimePermission() throws Exception {
+ grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
+ setAppOp(APP, ACCESS_FINE_LOCATION, MODE_FOREGROUND);
+
+ // revoked is the default state. Hence mark the permission as user set, so the permissions
+ // are even backed up
+ setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION));
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION));
+ assertEquals(MODE_FOREGROUND, getAppOp(APP, ACCESS_FINE_LOCATION));
+ });
+ }
+
+ /**
+ * Test backup and restore of foreground runtime permission.
+ */
+ public void testGrantForegroundRuntimePermission22() throws Exception {
+ setAppOp(APP22, ACCESS_FINE_LOCATION, MODE_FOREGROUND);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP22);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> assertEquals(MODE_FOREGROUND, getAppOp(APP22, ACCESS_FINE_LOCATION)));
+ }
+
+ /**
+ * Test backup and restore of foreground runtime permission.
+ */
+ public void testGrantForegroundAndBackgroundRuntimePermission() throws Exception {
+ grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
+ grantRuntimePermission(APP, ACCESS_BACKGROUND_LOCATION);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION));
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION));
+ assertEquals(MODE_ALLOWED, getAppOp(APP, ACCESS_FINE_LOCATION));
+ });
+ }
+
+ /**
+ * Test backup and restore of foreground runtime permission.
+ */
+ public void testGrantForegroundAndBackgroundRuntimePermission22() throws Exception {
+ // Set a marker
+ setAppOp(APP, WRITE_CONTACTS, MODE_IGNORED);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP22);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> {
+ // Wait for marker
+ assertEquals(MODE_IGNORED, getAppOp(APP, WRITE_CONTACTS));
+
+ assertEquals(MODE_ALLOWED, getAppOp(APP, ACCESS_FINE_LOCATION));
+ });
+ }
+
+ /**
+ * Restore if the permission was reviewed
+ */
+ public void testRestorePermReviewed() throws Exception {
+ clearFlag(APP22, WRITE_CONTACTS, FLAG_PERMISSION_REVIEW_REQUIRED);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP22);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> assertFalse(
+ isFlagSet(APP22, WRITE_CONTACTS, FLAG_PERMISSION_REVIEW_REQUIRED)));
+ }
+
+ /**
+ * Restore if the permission was user set
+ */
+ public void testRestoreUserSet() throws Exception {
+ setFlag(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> assertTrue(isFlagSet(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET)));
+ }
+
+ /**
+ * Restore if the permission was user fixed
+ */
+ public void testRestoreUserFixed() throws Exception {
+ setFlag(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_FIXED);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> assertTrue(isFlagSet(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_FIXED)));
+ }
+
+ /**
+ * Restoring of a flag should not grant the permission
+ */
+ public void testRestoreOfFlagDoesNotGrantPermission() throws Exception {
+ setFlag(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_FIXED);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+ resetApp(APP);
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ eventually(() -> assertEquals(PERMISSION_DENIED, checkPermission(APP, WRITE_CONTACTS)));
+ }
+
+ /**
+ * Test backup and delayed restore of regular runtime permission.
+ */
+ public void testDelayedRestore() throws IOException {
+ grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
+
+ setAppOp(APP22, READ_CONTACTS, MODE_IGNORED);
+
+ mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+
+ uninstall(APP);
+ uninstall(APP22);
+
+ try {
+ mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+ install(APP_APK);
+
+ eventually(() -> assertEquals(PERMISSION_GRANTED,
+ checkPermission(APP, ACCESS_FINE_LOCATION)));
+
+ install(APP22_APK);
+
+ eventually(() -> assertEquals(MODE_IGNORED, getAppOp(APP22, READ_CONTACTS)));
+ } finally {
+ install(APP_APK);
+ install(APP22_APK);
}
}
- private void revokeRuntimePermission(String permission) {
- if (checkPermission(permission) == PackageManager.PERMISSION_GRANTED) {
- getInstrumentation().getUiAutomation().revokeRuntimePermission(APP_PACKAGE, permission);
- assertEquals(PackageManager.PERMISSION_DENIED, checkPermission(permission));
+ private void install(String apk) {
+ ShellUtils.runShellCommand("pm install -r " + apk);
+ }
+
+ private void uninstall(String packageName) {
+ ShellUtils.runShellCommand("pm uninstall " + packageName);
+ }
+
+ private void resetApp(String packageName) {
+ ShellUtils.runShellCommand("pm clear " + packageName);
+ ShellUtils.runShellCommand("appops reset " + packageName);
+ }
+
+ /**
+ * Make sure that a {@link Runnable} eventually finishes without throwing a {@link
+ * Exception}.
+ *
+ * @param r The {@link Runnable} to run.
+ */
+ public static void eventually(@NonNull Runnable r) {
+ long start = System.currentTimeMillis();
+
+ while (true) {
+ try {
+ r.run();
+ return;
+ } catch (Throwable e) {
+ if (System.currentTimeMillis() - start < TIMEOUT_MILLIS) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignored) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ private void setFlag(String app, String permission, int flag) {
+ runWithShellPermissionIdentity(
+ () -> sContext.getPackageManager().updatePermissionFlags(permission, app,
+ flag, flag, sContext.getUser()));
+ }
+
+ private void clearFlag(String app, String permission, int flag) {
+ runWithShellPermissionIdentity(
+ () -> sContext.getPackageManager().updatePermissionFlags(permission, app,
+ flag, 0, sContext.getUser()));
+ }
+
+ private boolean isFlagSet(String app, String permission, int flag) {
+ try {
+ return (callWithShellPermissionIdentity(
+ () -> sContext.getPackageManager().getPermissionFlags(permission, app,
+ sContext.getUser())) & flag) == flag;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private int checkPermission(String app, String permission) {
+ return sContext.getPackageManager().checkPermission(permission, app);
+ }
+
+ private void grantRuntimePermission(String app, String permission) {
+ if (checkPermission(app, permission) != PERMISSION_GRANTED) {
+ getInstrumentation().getUiAutomation().grantRuntimePermission(app, permission);
+ assertEquals(PERMISSION_GRANTED, checkPermission(app, permission));
+ }
+ }
+
+ private void setAppOp(String app, String permission, int mode) {
+ runWithShellPermissionIdentity(
+ () -> sContext.getSystemService(AppOpsManager.class).setUidMode(
+ permissionToOp(permission),
+ sContext.getPackageManager().getPackageUid(app, 0), mode));
+ }
+
+ private int getAppOp(String app, String permission) {
+ try {
+ return callWithShellPermissionIdentity(
+ () -> sContext.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
+ permissionToOp(permission),
+ sContext.getPackageManager().getPackageUid(app, 0), app));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
}
}