diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 9560527857a92..9facc7e65b167 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9703,6 +9703,8 @@ package android.os { } public class Environment { + method @NonNull public static java.io.File getDataCePackageDirectoryForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle, @NonNull String); + method @NonNull public static java.io.File getDataDePackageDirectoryForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle, @NonNull String); method @NonNull public static java.util.Collection getInternalMediaDirectories(); method @NonNull public static java.io.File getOdmDirectory(); method @NonNull public static java.io.File getOemDirectory(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 0bbae8277b8a7..7cee7e05feaf7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2071,7 +2071,7 @@ package android.os.storage { public class StorageManager { method public long computeStorageCacheBytes(@NonNull java.io.File); - method @NonNull public static java.util.UUID convert(@NonNull String); + method @NonNull public static java.util.UUID convert(@Nullable String); method @NonNull public static String convert(@NonNull java.util.UUID); method @Nullable public String getCloudMediaProvider(); method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int); diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index ffa9507ebc2ad..536ef31f334af 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -18,6 +18,7 @@ package android.os; import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -44,6 +45,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.UUID; /** * Provides access to environment variables. @@ -551,12 +553,37 @@ public class Environment { } /** {@hide} */ - public static File getDataUserCePackageDirectory(String volumeUuid, int userId, - String packageName) { + @NonNull + public static File getDataUserCePackageDirectory(@Nullable String volumeUuid, int userId, + @NonNull String packageName) { // TODO: keep consistent with installd return new File(getDataUserCeDirectory(volumeUuid, userId), packageName); } + /** + * Retrieve the credential encrypted data directory for a specific package of a specific user. + * This is equivalent to {@link ApplicationInfo#credentialProtectedDataDir}, exposed because + * fetching a full {@link ApplicationInfo} instance may be expensive if all the caller needs + * is this directory. + * + * @param storageUuid The storage volume for this directory, usually retrieved from a + * {@link StorageManager} API or {@link ApplicationInfo#storageUuid}. + * @param user The user this directory is for. + * @param packageName The app this directory is for. + * + * @see ApplicationInfo#credentialProtectedDataDir + * @return A file to the directory. + * + * @hide + */ + @SystemApi + @NonNull + public static File getDataCePackageDirectoryForUser(@NonNull UUID storageUuid, + @NonNull UserHandle user, @NonNull String packageName) { + var volumeUuid = StorageManager.convert(storageUuid); + return getDataUserCePackageDirectory(volumeUuid, user.getIdentifier(), packageName); + } + /** {@hide} */ public static File getDataUserDeDirectory(String volumeUuid) { return new File(getDataDirectory(volumeUuid), DIR_USER_DE); @@ -568,12 +595,37 @@ public class Environment { } /** {@hide} */ - public static File getDataUserDePackageDirectory(String volumeUuid, int userId, - String packageName) { + @NonNull + public static File getDataUserDePackageDirectory(@Nullable String volumeUuid, int userId, + @NonNull String packageName) { // TODO: keep consistent with installd return new File(getDataUserDeDirectory(volumeUuid, userId), packageName); } + /** + * Retrieve the device encrypted data directory for a specific package of a specific user. This + * is equivalent to {@link ApplicationInfo#deviceProtectedDataDir}, exposed because fetching a + * full {@link ApplicationInfo} instance may be expensive if all the caller needs is this + * directory. + * + * @param storageUuid The storage volume for this directory, usually retrieved from a + * {@link StorageManager} API or {@link ApplicationInfo#storageUuid}. + * @param user The user this directory is for. + * @param packageName The app this directory is for. + * + * @see ApplicationInfo#deviceProtectedDataDir + * @return A file to the directory. + * + * @hide + */ + @SystemApi + @NonNull + public static File getDataDePackageDirectoryForUser(@NonNull UUID storageUuid, + @NonNull UserHandle user, @NonNull String packageName) { + var volumeUuid = StorageManager.convert(storageUuid); + return getDataUserDePackageDirectory(volumeUuid, user.getIdentifier(), packageName); + } + /** * Return preloads directory. *

This directory may contain pre-loaded content such as diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 3a5666283ae2d..cc5426631a7b2 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -84,6 +84,15 @@ "include-filter": "android.os.cts.SharedMemoryTest" } ] + }, + { + "file_patterns": ["Environment[^/]*\\.java"], + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.os.EnvironmentTest" + } + ] } ], "postsubmit": [ diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 78994205e5e7e..a72ccadd98295 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2742,7 +2742,8 @@ public class StorageManager { /** {@hide} */ @TestApi - public static @NonNull UUID convert(@NonNull String uuid) { + public static @NonNull UUID convert(@Nullable String uuid) { + // UUID_PRIVATE_INTERNAL is null, so this accepts nullable input if (Objects.equals(uuid, UUID_PRIVATE_INTERNAL)) { return UUID_DEFAULT; } else if (Objects.equals(uuid, UUID_PRIMARY_PHYSICAL)) { diff --git a/core/tests/coretests/src/android/os/EnvironmentTest.java b/core/tests/coretests/src/android/os/EnvironmentTest.java index 8e63a0fe3364b..ef38cdec0d633 100644 --- a/core/tests/coretests/src/android/os/EnvironmentTest.java +++ b/core/tests/coretests/src/android/os/EnvironmentTest.java @@ -22,12 +22,12 @@ import static android.os.Environment.HAS_DOWNLOADS; import static android.os.Environment.HAS_OTHER; import static android.os.Environment.classifyExternalStorageDirectory; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; -import android.app.AppOpsManager; import android.content.Context; +import android.os.storage.StorageManager; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -38,6 +38,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; +import java.util.ArrayList; +import java.util.UUID; +import java.util.function.BiFunction; @RunWith(AndroidJUnit4.class) public class EnvironmentTest { @@ -104,4 +107,42 @@ public class EnvironmentTest { Environment.buildPath(dir, "Taxes.pdf").createNewFile(); assertEquals(HAS_OTHER, classifyExternalStorageDirectory(dir)); } + + @Test + public void testDataCePackageDirectoryForUser() { + testDataPackageDirectoryForUser( + (uuid, userHandle) -> Environment.getDataCePackageDirectoryForUser( + uuid, userHandle, getContext().getPackageName()), + (uuid, user) -> Environment.getDataUserCePackageDirectory( + uuid, user, getContext().getPackageName()) + ); + } + + @Test + public void testDataDePackageDirectoryForUser() { + testDataPackageDirectoryForUser( + (uuid, userHandle) -> Environment.getDataDePackageDirectoryForUser( + uuid, userHandle, getContext().getPackageName()), + (uuid, user) -> Environment.getDataUserDePackageDirectory( + uuid, user, getContext().getPackageName()) + ); + } + + private void testDataPackageDirectoryForUser( + BiFunction publicApi, + BiFunction hideApi) { + var uuids = new ArrayList(); + uuids.add(null); // Private internal + uuids.add("primary_physical"); + uuids.add("system"); + uuids.add("3939-3939"); // FAT Volume + uuids.add("57554103-df3e-4475-ae7a-8feba49353ac"); // Random valid UUID + var userHandle = UserHandle.of(0); + + // Check that the @hide method is consistent with the public API + for (String uuid : uuids) { + assertThat(publicApi.apply(StorageManager.convert(uuid), userHandle)) + .isEqualTo(hideApi.apply(uuid, 0)); + } + } }