Expose Environment device/credential encrypted app storage directories
ApplicationInfo is expensive to build, especially when using the new PackageState APIs which don't require ApplicationInfo at all. To allow access to the dataDir field without ApplicationInfo, this exposes methods on Environment to build them dynamically based on volume UUID, user, and app package name. This avoids having to build/cache the strings in system server and consolidates the file system related APIs into Environment as a single source of truth. This also fixes the @TestApi StorageManager#convert so that its String parameter is marked @Nullable, which works because UUID_PRIVATE_INTERNAL is defined as nullable. This allows the method to be used in Kotlin, as the Kotlin compiler will enforce nullability annotations, preventing the UUID_PRIVATE_INTERNAL value from being tested without the right annotation on the parameter. Bug: 245943991 Test: atest android.os.EnvironmentTest Test: atest android.os.cts.EnvironmentTest Change-Id: I5983ee22a27f566352fd0de03f0ee421a0bc390f
This commit is contained in:
@@ -9626,6 +9626,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<java.io.File> getInternalMediaDirectories();
|
||||
method @NonNull public static java.io.File getOdmDirectory();
|
||||
method @NonNull public static java.io.File getOemDirectory();
|
||||
|
||||
@@ -2047,7 +2047,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);
|
||||
|
||||
@@ -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.
|
||||
* <p>This directory may contain pre-loaded content such as
|
||||
|
||||
@@ -84,6 +84,15 @@
|
||||
"include-filter": "android.os.cts.SharedMemoryTest"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file_patterns": ["Environment[^/]*\\.java"],
|
||||
"name": "FrameworksCoreTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "android.os.EnvironmentTest"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"postsubmit": [
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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<UUID, UserHandle, File> publicApi,
|
||||
BiFunction<String, Integer, File> hideApi) {
|
||||
var uuids = new ArrayList<String>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user