Merge "Add test for LocalCallingIdentity changes" into rvc-dev
diff --git a/tests/jni/FuseDaemonTest/Android.bp b/tests/jni/FuseDaemonTest/Android.bp
index 35cc9df..1208e1d 100644
--- a/tests/jni/FuseDaemonTest/Android.bp
+++ b/tests/jni/FuseDaemonTest/Android.bp
@@ -26,6 +26,22 @@
sdk_version: "test_current",
srcs: ["FilePathAccessTestHelper/src/**/*.java"],
}
+android_test_helper_app {
+ name: "TestAppC",
+ manifest: "FilePathAccessTestHelper/TestAppC.xml",
+ static_libs: ["androidx.test.rules", "tests-fusedaemon-lib"],
+ sdk_version: "test_current",
+ srcs: ["FilePathAccessTestHelper/src/**/*.java"],
+}
+android_test_helper_app {
+ name: "TestAppCLegacy",
+ manifest: "FilePathAccessTestHelper/TestAppCLegacy.xml",
+ static_libs: ["androidx.test.rules", "tests-fusedaemon-lib"],
+ sdk_version: "test_current",
+ target_sdk_version: "28",
+ srcs: ["FilePathAccessTestHelper/src/**/*.java"],
+}
+
android_test {
name: "FuseDaemonTest",
manifest: "AndroidManifest.xml",
@@ -36,9 +52,10 @@
java_resources: [
":TestAppA",
":TestAppB",
+ ":TestAppC",
+ ":TestAppCLegacy",
]
}
-
android_test {
name: "FuseDaemonLegacyTest",
manifest: "legacy/AndroidManifest.xml",
diff --git a/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/TestAppC.xml b/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/TestAppC.xml
new file mode 100644
index 0000000..f3f3c3d
--- /dev/null
+++ b/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/TestAppC.xml
@@ -0,0 +1,36 @@
+<?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="com.android.tests.fused.testapp.C"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+ <application android:label="TestAppC">
+ <activity android:name="com.android.tests.fused.FilePathAccessTestHelper">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/TestAppCLegacy.xml b/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/TestAppCLegacy.xml
new file mode 100644
index 0000000..a31ee47
--- /dev/null
+++ b/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/TestAppCLegacy.xml
@@ -0,0 +1,34 @@
+<?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="com.android.tests.fused.testapp.C"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application android:label="TestAppCLegacy">
+ <activity android:name="com.android.tests.fused.FilePathAccessTestHelper">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/src/com/android/tests/fused/FilePathAccessTestHelper.java b/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/src/com/android/tests/fused/FilePathAccessTestHelper.java
index 7f6a485..f7af146 100644
--- a/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/src/com/android/tests/fused/FilePathAccessTestHelper.java
+++ b/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/src/com/android/tests/fused/FilePathAccessTestHelper.java
@@ -22,7 +22,10 @@
import static com.android.tests.fused.lib.TestUtils.DELETE_FILE_QUERY;
import static com.android.tests.fused.lib.TestUtils.INTENT_EXCEPTION;
import static com.android.tests.fused.lib.TestUtils.INTENT_EXTRA_PATH;
+import static com.android.tests.fused.lib.TestUtils.OPEN_FILE_FOR_READ_QUERY;
+import static com.android.tests.fused.lib.TestUtils.OPEN_FILE_FOR_WRITE_QUERY;
import static com.android.tests.fused.lib.TestUtils.QUERY_TYPE;
+import static com.android.tests.fused.lib.TestUtils.canOpen;
import android.app.Activity;
import android.content.Intent;
@@ -54,7 +57,9 @@
break;
case CREATE_FILE_QUERY:
case DELETE_FILE_QUERY:
- createOrDeleteFile(queryType);
+ case OPEN_FILE_FOR_READ_QUERY:
+ case OPEN_FILE_FOR_WRITE_QUERY:
+ accessFile(queryType);
break;
case EXIF_METADATA_QUERY:
sendMetadata(queryType);
@@ -99,7 +104,7 @@
}
}
- private void createOrDeleteFile(String queryType) {
+ private void accessFile(String queryType) {
if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
final File file = new File(filePath);
@@ -109,9 +114,13 @@
returnStatus = file.createNewFile();
} else if (queryType.equals(DELETE_FILE_QUERY)) {
returnStatus = file.delete();
+ } else if (queryType.equals(OPEN_FILE_FOR_READ_QUERY)) {
+ returnStatus = canOpen(file, false /* forWrite */);
+ } else if (queryType.equals(OPEN_FILE_FOR_WRITE_QUERY)) {
+ returnStatus = canOpen(file, true /* forWrite */);
}
} catch(IOException e) {
- Log.e(TAG, "IOException occurred while creating/deleting " + filePath);
+ Log.e(TAG, "Failed to access file: " + filePath + ". Query type: " + queryType, e);
}
final Intent intent = new Intent(queryType);
intent.putExtra(queryType, returnStatus);
diff --git a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
index 10b063a..9d8a1a0 100644
--- a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
+++ b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
@@ -151,6 +151,23 @@
}
@Test
+ public void testCallingIdentityCacheInvalidation() throws Exception {
+ // General IO access
+ runDeviceTest("testReadStorageInvalidation");
+ runDeviceTest("testWriteStorageInvalidation");
+ // File manager access
+ runDeviceTest("testManageStorageInvalidation");
+ // Default gallery
+ runDeviceTest("testWriteImagesInvalidation");
+ runDeviceTest("testWriteVideoInvalidation");
+ // EXIF access
+ runDeviceTest("testAccessMediaLocationInvalidation");
+
+ runDeviceTest("testAppUpdateInvalidation");
+ runDeviceTest("testAppReinstallInvalidation");
+ }
+
+ @Test
public void testRenameFile() throws Exception {
runDeviceTest("testRenameFile");
}
diff --git a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
index 75735a5..97e39d4 100644
--- a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
+++ b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
@@ -62,6 +62,7 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -80,7 +81,8 @@
public static final String INTENT_EXCEPTION = "com.android.tests.fused.exception";
public static final String CREATE_FILE_QUERY = "com.android.tests.fused.createfile";
public static final String DELETE_FILE_QUERY = "com.android.tests.fused.deletefile";
-
+ public static final String OPEN_FILE_FOR_READ_QUERY = "com.android.tests.fused.openfile_read";
+ public static final String OPEN_FILE_FOR_WRITE_QUERY = "com.android.tests.fused.openfile_write";
public static final String STR_DATA1 = "Just some random text";
public static final String STR_DATA2 = "More arbitrary stuff";
@@ -91,47 +93,47 @@
private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
private static final long POLLING_SLEEP_MILLIS = 100;
- private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
- .getUiAutomation();
-
/**
* Grants {@link Manifest.permission#GRANT_RUNTIME_PERMISSIONS} to the given package.
*/
- public static void grantReadExternalStorage(String packageName) {
- sUiAutomation.adoptShellPermissionIdentity("android.permission.GRANT_RUNTIME_PERMISSIONS");
+ public static void grantPermission(String packageName, String permission) {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity("android.permission.GRANT_RUNTIME_PERMISSIONS");
try {
- sUiAutomation.grantRuntimePermission(packageName,
- Manifest.permission.READ_EXTERNAL_STORAGE);
+ uiAutomation.grantRuntimePermission(packageName, permission);
// Wait for OP_READ_EXTERNAL_STORAGE to get updated.
SystemClock.sleep(1000);
} finally {
- sUiAutomation.dropShellPermissionIdentity();
+ uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Revokes {@link Manifest.permission#GRANT_RUNTIME_PERMISSIONS} from the given package.
*/
- public static void revokeReadExternalStorage(String packageName) {
- sUiAutomation.adoptShellPermissionIdentity("android.permission.REVOKE_RUNTIME_PERMISSIONS");
+ public static void revokePermission(String packageName, String permission) {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity("android.permission.REVOKE_RUNTIME_PERMISSIONS");
try {
- sUiAutomation.revokeRuntimePermission(packageName,
- Manifest.permission.READ_EXTERNAL_STORAGE);
+ uiAutomation.revokeRuntimePermission(packageName, permission);
} finally {
- sUiAutomation.dropShellPermissionIdentity();
+ uiAutomation.dropShellPermissionIdentity();
}
}
public static void adoptShellPermissionIdentity(String... permissions) {
- sUiAutomation.adoptShellPermissionIdentity(permissions);
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(permissions);
}
public static void dropShellPermissionIdentity() {
- sUiAutomation.dropShellPermissionIdentity();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
}
public static String executeShellCommand(String cmd) throws Exception {
- try (FileInputStream output = new FileInputStream (sUiAutomation.executeShellCommand(cmd)
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try (FileInputStream output = new FileInputStream (uiAutomation.executeShellCommand(cmd)
.getFileDescriptor())) {
return new String(ByteStreams.toByteArray(output));
}
@@ -166,7 +168,7 @@
* <p>This method drops shell permission identity.
*/
public static boolean createFileAs(TestApp testApp, String path) throws Exception {
- return createOrDeleteFileFromTestApp(testApp, path, CREATE_FILE_QUERY);
+ return getResultFromTestApp(testApp, path, CREATE_FILE_QUERY);
}
/**
@@ -175,7 +177,7 @@
* <p>This method drops shell permission identity.
*/
public static boolean deleteFileAs(TestApp testApp, String path) throws Exception {
- return createOrDeleteFileFromTestApp(testApp, path, DELETE_FILE_QUERY);
+ return getResultFromTestApp(testApp, path, DELETE_FILE_QUERY);
}
/**
@@ -192,14 +194,25 @@
}
/**
+ * Makes the given {@code testApp} open a file for read or write.
+ *
+ * <p>This method drops shell permission identity.
+ */
+ public static boolean openFileAs(TestApp testApp, String path, boolean forWrite)
+ throws Exception {
+ return getResultFromTestApp(testApp, path,
+ forWrite ? OPEN_FILE_FOR_WRITE_QUERY : OPEN_FILE_FOR_READ_QUERY);
+ }
+
+ /**
* Installs a {@link TestApp} and may grant it storage permissions.
*/
public static void installApp(TestApp testApp, boolean grantStoragePermission)
throws Exception {
-
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
final String packageName = testApp.getPackageName();
- sUiAutomation.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES,
+ uiAutomation.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES);
if (InstallUtils.getInstalledVersion(packageName) != -1) {
Uninstall.packages(packageName);
@@ -207,10 +220,10 @@
Install.single(testApp).commit();
assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1);
if (grantStoragePermission) {
- grantReadExternalStorage(packageName);
+ grantPermission(packageName, Manifest.permission.READ_EXTERNAL_STORAGE);
}
} finally {
- sUiAutomation.dropShellPermissionIdentity();
+ uiAutomation.dropShellPermissionIdentity();
}
}
@@ -218,14 +231,15 @@
* Uninstalls a {@link TestApp}.
*/
public static void uninstallApp(TestApp testApp) throws Exception {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
final String packageName = testApp.getPackageName();
- sUiAutomation.adoptShellPermissionIdentity(Manifest.permission.DELETE_PACKAGES);
+ uiAutomation.adoptShellPermissionIdentity(Manifest.permission.DELETE_PACKAGES);
Uninstall.packages(packageName);
assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(-1);
} finally {
- sUiAutomation.dropShellPermissionIdentity();
+ uiAutomation.dropShellPermissionIdentity();
}
}
@@ -456,6 +470,22 @@
}
}
+ public static boolean canOpen(File file, boolean forWrite) {
+ if (forWrite) {
+ try (FileOutputStream fis = new FileOutputStream(file)) {
+ return true;
+ } catch (IOException expected) {
+ return false;
+ }
+ } else {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ return true;
+ } catch (IOException expected) {
+ return false;
+ }
+ }
+ }
+
public static void pollForExternalStorageState() throws Exception {
for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
if(Environment.getExternalStorageState(Environment.getExternalStorageDirectory())
@@ -534,13 +564,14 @@
* <p>This method drops shell permission identity.
*/
private static void forceStopApp(String packageName) throws Exception {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
- sUiAutomation.adoptShellPermissionIdentity(Manifest.permission.FORCE_STOP_PACKAGES);
+ uiAutomation.adoptShellPermissionIdentity(Manifest.permission.FORCE_STOP_PACKAGES);
getContext().getSystemService(ActivityManager.class).forceStopPackage(packageName);
Thread.sleep(1000);
} finally {
- sUiAutomation.dropShellPermissionIdentity();
+ uiAutomation.dropShellPermissionIdentity();
}
}
@@ -621,7 +652,7 @@
/**
* <p>This method drops shell permission identity.
*/
- private static boolean createOrDeleteFileFromTestApp(TestApp testApp, String dirPath,
+ private static boolean getResultFromTestApp(TestApp testApp, String dirPath,
String actionName) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final boolean[] appOutput = new boolean[1];
@@ -668,4 +699,4 @@
assertThat(c).isNotNull();
return c;
}
-}
\ No newline at end of file
+}
diff --git a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
index 54caffe..f676780 100644
--- a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
+++ b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
@@ -16,6 +16,7 @@
package com.android.tests.fused;
+import static android.app.AppOpsManager.permissionToOp;
import static android.os.SystemProperties.getBoolean;
import static android.provider.MediaStore.MediaColumns;
@@ -36,6 +37,7 @@
import static com.android.tests.fused.lib.TestUtils.assertCantRenameFile;
import static com.android.tests.fused.lib.TestUtils.assertFileContent;
import static com.android.tests.fused.lib.TestUtils.assertThrows;
+import static com.android.tests.fused.lib.TestUtils.canOpen;
import static com.android.tests.fused.lib.TestUtils.createFileAs;
import static com.android.tests.fused.lib.TestUtils.deleteFileAs;
import static com.android.tests.fused.lib.TestUtils.deleteFileAsNoThrow;
@@ -47,11 +49,13 @@
import static com.android.tests.fused.lib.TestUtils.getFileMimeTypeFromDatabase;
import static com.android.tests.fused.lib.TestUtils.getFileRowIdFromDatabase;
import static com.android.tests.fused.lib.TestUtils.getFileUri;
+import static com.android.tests.fused.lib.TestUtils.grantPermission;
import static com.android.tests.fused.lib.TestUtils.installApp;
import static com.android.tests.fused.lib.TestUtils.listAs;
+import static com.android.tests.fused.lib.TestUtils.openFileAs;
import static com.android.tests.fused.lib.TestUtils.openWithMediaProvider;
import static com.android.tests.fused.lib.TestUtils.readExifMetadataFromTestApp;
-import static com.android.tests.fused.lib.TestUtils.revokeReadExternalStorage;
+import static com.android.tests.fused.lib.TestUtils.revokePermission;
import static com.android.tests.fused.lib.TestUtils.uninstallApp;
import static com.android.tests.fused.lib.TestUtils.uninstallAppNoThrow;
import static com.android.tests.fused.lib.TestUtils.updateDisplayNameWithMediaProvider;
@@ -63,6 +67,7 @@
import static org.junit.Assume.assumeTrue;
+import android.Manifest;
import android.app.AppOpsManager;
import android.content.ContentResolver;
import android.database.Cursor;
@@ -77,6 +82,7 @@
import android.system.OsConstants;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.test.runner.AndroidJUnit4;
import com.android.cts.install.lib.TestApp;
@@ -89,7 +95,6 @@
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -139,10 +144,14 @@
"com.android.tests.fused.testapp.A", 1, false, "TestAppA.apk");
private static final TestApp TEST_APP_B = new TestApp("TestAppB",
"com.android.tests.fused.testapp.B", 1, false, "TestAppB.apk");
+ private static final TestApp TEST_APP_C = new TestApp("TestAppC",
+ "com.android.tests.fused.testapp.C", 1, false, "TestAppC.apk");
+ private static final TestApp TEST_APP_C_LEGACY = new TestApp("TestAppCLegacy",
+ "com.android.tests.fused.testapp.C", 1, false, "TestAppCLegacy.apk");
private static final String[] SYSTEM_GALERY_APPOPS = { AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES,
AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO };
- //TODO(b/150115615): used AppOpsManager#OPSTR_MANAGE_EXTERNAL_STORAGE once it's public API
- private static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";
+ private static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
+ permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
@Before
public void setUp() throws Exception {
@@ -380,22 +389,10 @@
assertThat(nonMediaFile.exists()).isTrue();
// But we can't access their content
- try (FileInputStream fis = new FileInputStream(mediaFile)) {
- fail("Opening for read succeeded when it should have failed: " + mediaFile);
- } catch (IOException expected) {}
-
- try (FileInputStream fis = new FileInputStream(nonMediaFile)) {
- fail("Opening for read succeeded when it should have failed: " + nonMediaFile);
- } catch (IOException expected) {}
-
- try (FileOutputStream fos = new FileOutputStream(mediaFile)) {
- fail("Opening for write succeeded when it should have failed: " + mediaFile);
- } catch (IOException expected) {}
-
- try (FileOutputStream fos = new FileOutputStream(nonMediaFile)) {
- fail("Opening for write succeeded when it should have failed: " + nonMediaFile);
- } catch (IOException expected) {}
-
+ assertThat(canOpen(mediaFile, /* forWrite */ false)).isFalse();
+ assertThat(canOpen(nonMediaFile, /* forWrite */ true)).isFalse();
+ assertThat(canOpen(mediaFile, /* forWrite */ false)).isFalse();
+ assertThat(canOpen(nonMediaFile, /* forWrite */ true)).isFalse();
} finally {
deleteFileAsNoThrow(TEST_APP_A, nonMediaFile.getPath());
deleteFileAsNoThrow(TEST_APP_A, mediaFile.getPath());
@@ -560,7 +557,8 @@
assertThat(listAs(TEST_APP_B, dir.getPath())).containsExactly(videoFileName);
// Revoke storage permission for TEST_APP_B
- revokeReadExternalStorage(TEST_APP_B.getPackageName());
+ revokePermission(TEST_APP_B.getPackageName(),
+ Manifest.permission.READ_EXTERNAL_STORAGE);
// TEST_APP_B without storage permission should see TEST_DIRECTORY in DCIM and should
// not see new file in new TEST_DIRECTORY.
assertThat(listAs(TEST_APP_B, DCIM_DIR.getPath())).contains(TEST_DIRECTORY_NAME);
@@ -908,6 +906,175 @@
}
@Test
+ public void testReadStorageInvalidation() throws Exception {
+ testAppOpInvalidation(TEST_APP_C, new File(DCIM_DIR, "read_storage.jpg"),
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, /* forWrite */ false);
+ }
+
+ @Test
+ public void testWriteStorageInvalidation() throws Exception {
+ testAppOpInvalidation(TEST_APP_C_LEGACY, new File(DCIM_DIR, "write_storage.jpg"),
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE, /* forWrite */ true);
+ }
+
+ @Test
+ public void testManageStorageInvalidation() throws Exception {
+ testAppOpInvalidation(TEST_APP_C, new File(DOWNLOAD_DIR, "manage_storage.pdf"),
+ /* permission */ null, OPSTR_MANAGE_EXTERNAL_STORAGE, /* forWrite */ true);
+ }
+
+ @Test
+ public void testWriteImagesInvalidation() throws Exception {
+ testAppOpInvalidation(TEST_APP_C, new File(DCIM_DIR, "write_images.jpg"),
+ /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, /* forWrite */ true);
+ }
+
+ @Test
+ public void testWriteVideoInvalidation() throws Exception {
+ testAppOpInvalidation(TEST_APP_C, new File(DCIM_DIR, "write_video.mp4"),
+ /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, /* forWrite */ true);
+ }
+
+ @Test
+ public void testAccessMediaLocationInvalidation() throws Exception {
+ File imgFile = new File(DCIM_DIR, "access_media_location.jpg");
+
+ try {
+ // Setup image with sensitive data on external storage
+ HashMap<String, String> originalExif = getExifMetadataFromRawResource(
+ R.raw.img_with_metadata);
+ try (InputStream in = getContext().getResources().openRawResource(
+ R.raw.img_with_metadata);
+ OutputStream out = new FileOutputStream(imgFile)) {
+ // Dump the image we have to external storage
+ FileUtils.copy(in, out);
+ }
+ HashMap<String, String> exif = getExifMetadata(imgFile);
+ assertExifMetadataMatch(exif, originalExif);
+
+ // Install test app
+ installApp(TEST_APP_C, /* grantStoragePermissions */ true);
+
+ // Grant A_M_L and verify access to sensitive data
+ grantPermission(TEST_APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
+ HashMap<String, String> exifFromTestApp = readExifMetadataFromTestApp(TEST_APP_C,
+ imgFile.getPath());
+ assertExifMetadataMatch(exifFromTestApp, originalExif);
+
+ // Revoke A_M_L and verify sensitive data redaction
+ revokePermission(TEST_APP_C.getPackageName(),
+ Manifest.permission.ACCESS_MEDIA_LOCATION);
+ exifFromTestApp = readExifMetadataFromTestApp(TEST_APP_C,
+ imgFile.getPath());
+ assertExifMetadataMismatch(exifFromTestApp, originalExif);
+
+ // Re-grant A_M_L and verify access to sensitive data
+ grantPermission(TEST_APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
+ exifFromTestApp = readExifMetadataFromTestApp(TEST_APP_C,
+ imgFile.getPath());
+ assertExifMetadataMatch(exifFromTestApp, originalExif);
+ } finally {
+ imgFile.delete();
+ uninstallAppNoThrow(TEST_APP_C);
+ }
+ }
+
+ @Test
+ public void testAppUpdateInvalidation() throws Exception {
+ File file = new File(DCIM_DIR, "app_update.jpg");
+ try {
+ assertThat(file.createNewFile()).isTrue();
+
+ // Install legacy
+ installApp(TEST_APP_C_LEGACY, /* grantStoragePermissions */ true);
+ grantPermission(TEST_APP_C_LEGACY.getPackageName(),
+ Manifest.permission.WRITE_EXTERNAL_STORAGE); // Grants write access for legacy
+ // Legacy app can read and write media files contributed by others
+ assertThat(openFileAs(TEST_APP_C_LEGACY, file.getPath(), /* forWrite */ false))
+ .isTrue();
+ assertThat(openFileAs(TEST_APP_C_LEGACY, file.getPath(), /* forWrite */ true)).isTrue();
+
+ // Update to non-legacy
+ installApp(TEST_APP_C, /* grantStoragePermissions */ true);
+ grantPermission(TEST_APP_C_LEGACY.getPackageName(),
+ Manifest.permission.WRITE_EXTERNAL_STORAGE); // No effect for non-legacy
+ // Non-legacy app can read media files contributed by others
+ assertThat(openFileAs(TEST_APP_C, file.getPath(), /* forWrite */ false)).isTrue();
+ // But cannot write
+ assertThat(openFileAs(TEST_APP_C, file.getPath(), /* forWrite */ true)).isFalse();
+ } finally {
+ file.delete();
+ uninstallAppNoThrow(TEST_APP_C);
+ }
+ }
+
+ @Test
+ public void testAppReinstallInvalidation() throws Exception {
+ File file = new File(DCIM_DIR, "app_reinstall.jpg");
+
+ try {
+ assertThat(file.createNewFile()).isTrue();
+
+ // Install
+ installApp(TEST_APP_C, /* grantStoragePermissions */ true);
+ assertThat(openFileAs(TEST_APP_C, file.getPath(), /* forWrite */ false)).isTrue();
+
+ // Re-install
+ uninstallAppNoThrow(TEST_APP_C);
+ installApp(TEST_APP_C, /* grantStoragePermissions */ false);
+ assertThat(openFileAs(TEST_APP_C, file.getPath(), /* forWrite */ false)).isFalse();
+ } finally {
+ file.delete();
+ uninstallAppNoThrow(TEST_APP_C);
+ }
+ }
+
+ private void testAppOpInvalidation(TestApp app, File file, @Nullable String permission,
+ String opstr, boolean forWrite) throws Exception {
+ try {
+ installApp(app, false);
+ assertThat(file.createNewFile()).isTrue();
+ assertAppOpInvalidation(app, file, permission, opstr, forWrite);
+ } finally {
+ file.delete();
+ uninstallApp(app);
+ }
+ }
+
+ /** If {@code permission} is null, appops are flipped, otherwise permissions are flipped */
+ private void assertAppOpInvalidation(TestApp app, File file, @Nullable String permission,
+ String opstr, boolean forWrite) throws Exception {
+ String packageName = app.getPackageName();
+ int uid = getContext().getPackageManager().getPackageUid(packageName, 0);
+
+ // Deny
+ if (permission != null) {
+ revokePermission(packageName, permission);
+ } else {
+ denyAppOpsToUid(uid, opstr);
+ }
+ assertThat(openFileAs(app, file.getPath(), forWrite)).isFalse();
+
+ // Grant
+ if (permission != null) {
+ grantPermission(packageName, permission);
+ } else {
+ allowAppOpsToUid(uid, opstr);
+ }
+ assertThat(openFileAs(app, file.getPath(), forWrite)).isTrue();
+
+ // Deny
+ if (permission != null) {
+ revokePermission(packageName, permission);
+ } else {
+ denyAppOpsToUid(uid, opstr);
+ }
+ assertThat(openFileAs(app, file.getPath(), forWrite)).isFalse();
+ }
+
+ @Test
public void testSystemGalleryAppHasFullAccessToImages() throws Exception {
final File otherAppImageFile = new File(DCIM_DIR, "other_" + IMAGE_FILE_NAME);
final File topLevelImageFile = new File(EXTERNAL_STORAGE_DIR, IMAGE_FILE_NAME);
@@ -961,16 +1128,9 @@
assertThat(createFileAs(TEST_APP_A, otherAppAudioFile.getPath())).isTrue();
assertThat(otherAppAudioFile.exists()).isTrue();
- // Assert we can't write to the file
- try (FileInputStream fis = new FileInputStream(otherAppAudioFile)) {
- fail("Opening for read succeeded when it should have failed: " + otherAppAudioFile);
- } catch (IOException expected) {}
-
- // Assert we can't read from the file
- try (FileOutputStream fos = new FileOutputStream(otherAppAudioFile)) {
- fail("Opening for write succeeded when it should have failed: "
- + otherAppAudioFile);
- } catch (IOException expected) {}
+ // Assert we can't access the file
+ assertThat(canOpen(otherAppAudioFile, /* forWrite */ false)).isFalse();
+ assertThat(canOpen(otherAppAudioFile, /* forWrite */ true)).isFalse();
// Assert we can't delete the file
assertThat(otherAppAudioFile.delete()).isFalse();