diff --git a/api/test-current.txt b/api/test-current.txt
index 219258ef50b04..7deac26e4741b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -12,6 +12,7 @@ package android {
field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
+ field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
@@ -629,6 +630,9 @@ package android.app.usage {
public class StorageStatsManager {
method public boolean isQuotaSupported(@NonNull java.util.UUID);
method public boolean isReservedSupported(@NonNull java.util.UUID);
+ method @NonNull @WorkerThread public java.util.Collection queryCratesForPackage(@NonNull java.util.UUID, @NonNull String, @NonNull android.os.UserHandle) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull @WorkerThread public java.util.Collection queryCratesForUid(@NonNull java.util.UUID, int) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_CRATES) @WorkerThread public java.util.Collection queryCratesForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
}
public final class UsageStatsManager {
@@ -2314,6 +2318,17 @@ package android.os.image {
package android.os.storage {
+ public final class CrateInfo implements android.os.Parcelable {
+ ctor public CrateInfo(@NonNull CharSequence, long);
+ ctor public CrateInfo(@NonNull CharSequence);
+ method @Nullable public static android.os.storage.CrateInfo copyFrom(int, @Nullable String, @Nullable String);
+ method public int describeContents();
+ method public long getExpirationMillis();
+ method @NonNull public CharSequence getLabel();
+ method public void writeToParcel(@Nullable android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator CREATOR;
+ }
+
public class StorageManager {
method public static boolean hasIsolatedStorage();
}
diff --git a/core/java/android/app/usage/IStorageStatsManager.aidl b/core/java/android/app/usage/IStorageStatsManager.aidl
index 7eacc8996bb94..b5036da33a954 100644
--- a/core/java/android/app/usage/IStorageStatsManager.aidl
+++ b/core/java/android/app/usage/IStorageStatsManager.aidl
@@ -18,6 +18,8 @@ package android.app.usage;
import android.app.usage.StorageStats;
import android.app.usage.ExternalStorageStats;
+import android.content.pm.ParceledListSlice;
+import android.os.storage.CrateInfo;
/** {@hide} */
interface IStorageStatsManager {
@@ -31,4 +33,10 @@ interface IStorageStatsManager {
StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage);
StorageStats queryStatsForUser(String volumeUuid, int userId, String callingPackage);
ExternalStorageStats queryExternalStatsForUser(String volumeUuid, int userId, String callingPackage);
+ ParceledListSlice /* CrateInfo */ queryCratesForPackage(String volumeUuid, String packageName,
+ int userId, String callingPackage);
+ ParceledListSlice /* CrateInfo */ queryCratesForUid(String volumeUuid, int uid,
+ String callingPackage);
+ ParceledListSlice /* CrateInfo */ queryCratesForUser(String volumeUuid, int userId,
+ String callingPackage);
}
diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java
index a86c27a03358c..eecf0920fd24f 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -20,6 +20,7 @@ import static android.os.storage.StorageManager.convert;
import android.annotation.BytesLong;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.WorkerThread;
@@ -27,15 +28,19 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.storage.CrateInfo;
import android.os.storage.StorageManager;
import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.IOException;
+import java.util.Collection;
+import java.util.Objects;
import java.util.UUID;
/**
@@ -347,4 +352,100 @@ public class StorageStatsManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Return all of crate information for the specified storageUuid, packageName, and
+ * userHandle.
+ *
+ * @param storageUuid the UUID of the storage volume you're interested in,
+ * such as {@link StorageManager#UUID_DEFAULT}.
+ * @param uid the uid you're interested in.
+ * @return the collection of crate information.
+ * @throws PackageManager.NameNotFoundException when the package name is not found.
+ * @throws IOException cause by IO, not support, or the other reasons.
+ * @hide
+ */
+ @TestApi
+ @WorkerThread
+ @NonNull
+ public Collection queryCratesForUid(@NonNull UUID storageUuid,
+ int uid) throws IOException, PackageManager.NameNotFoundException {
+ try {
+ ParceledListSlice crateInfoList =
+ mService.queryCratesForUid(convert(storageUuid), uid,
+ mContext.getOpPackageName());
+ return Objects.requireNonNull(crateInfoList).getList();
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ e.maybeRethrow(IOException.class);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return all of crates information for the specified storageUuid, packageName, and
+ * userHandle.
+ *
+ * @param storageUuid the UUID of the storage volume you're interested in,
+ * such as {@link StorageManager#UUID_DEFAULT}.
+ * @param packageName the package name you're interested in.
+ * @param user the user you're interested in.
+ * @return the collection of crate information.
+ * @throws PackageManager.NameNotFoundException when the package name is not found.
+ * @throws IOException cause by IO, not support, or the other reasons.
+ * @hide
+ */
+ @WorkerThread
+ @TestApi
+ @NonNull
+ public Collection queryCratesForPackage(@NonNull UUID storageUuid,
+ @NonNull String packageName, @NonNull UserHandle user)
+ throws PackageManager.NameNotFoundException, IOException {
+ try {
+ ParceledListSlice crateInfoList =
+ mService.queryCratesForPackage(convert(storageUuid), packageName,
+ user.getIdentifier(), mContext.getOpPackageName());
+ return Objects.requireNonNull(crateInfoList).getList();
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ e.maybeRethrow(IOException.class);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return all of crate information for the specified storageUuid, packageName, and
+ * userHandle.
+ *
+ * @param storageUuid the UUID of the storage volume you're interested in,
+ * such as {@link StorageManager#UUID_DEFAULT}.
+ * @param user the user you're interested in.
+ * @return the collection of crate information.
+ * @throws PackageManager.NameNotFoundException when the package name is not found.
+ * @throws IOException cause by IO, not support, or the other reasons.
+ * @hide
+ */
+ @WorkerThread
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_CRATES)
+ @NonNull
+ public Collection queryCratesForUser(@NonNull UUID storageUuid,
+ @NonNull UserHandle user) throws PackageManager.NameNotFoundException, IOException {
+ try {
+ ParceledListSlice crateInfoList =
+ mService.queryCratesForUser(convert(storageUuid), user.getIdentifier(),
+ mContext.getOpPackageName());
+ return Objects.requireNonNull(crateInfoList).getList();
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ e.maybeRethrow(IOException.class);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/os/storage/CrateInfo.aidl b/core/java/android/os/storage/CrateInfo.aidl
new file mode 100644
index 0000000000000..dd910532c0a7d
--- /dev/null
+++ b/core/java/android/os/storage/CrateInfo.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+package android.os.storage;
+
+/**
+ * @hide
+ */
+parcelable CrateInfo;
diff --git a/core/java/android/os/storage/CrateInfo.java b/core/java/android/os/storage/CrateInfo.java
new file mode 100644
index 0000000000000..406aab3488c71
--- /dev/null
+++ b/core/java/android/os/storage/CrateInfo.java
@@ -0,0 +1,282 @@
+/*
+ * 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.
+ */
+
+package android.os.storage;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.usage.StorageStatsManager;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.UUID;
+
+/**
+ * The CrateInfo describe the crate information.
+ *
+ * It describe the following items.
+ *
+ * - The crate id that is the name of the child directory in
+ * {@link Context#getCrateDir(String)}
+ * - Label to provide human readable text for the users.
+ * - Expiration information. When the crate is expired and the run .
+ *
+ *
for the directory
+ *
+ * @hide
+ */
+@TestApi
+public final class CrateInfo implements Parcelable {
+ private static final String TAG = "CrateInfo";
+
+ /**
+ * The following member fields whose value are set by apps and retrieved by system_server.
+ */
+ private CharSequence mLabel;
+ @CurrentTimeMillisLong
+ private long mExpiration;
+
+ /**
+ * The following member fields whose value are retrieved by installd.
+ * {@link android.app.usage.StorageStatsManager#queryCratesForUser(UUID, UserHandle)} query
+ * all of crates for the specified UserHandle. That means the return crate list whose elements
+ * may have the same userId but different package name. Each crate needs the information to tell
+ * the caller from where package comes.
+ *
+ */
+ private int mUid;
+
+ /**
+ * The following member fields whose value are retrieved by installd.
+ * Both {@link StorageStatsManager#queryCratesForUid(UUID, int)} and
+ * {@link android.app.usage.StorageStatsManager#queryCratesForUser(UUID, UserHandle)} query
+ * all of crates for the specified uid or userId. That means the return crate list whose
+ * elements may have the same uid or userId but different package name. Each crate needs the
+ * information to tell the caller from where package comes.
+ *
+ */
+ @Nullable
+ private String mPackageName;
+
+ /**
+ * The following member fields whose value are retrieved by system_server.
+ *
+ * The child directories in {@link Context#getCrateDir(String)} are crates. Each directories
+ * is a crate. The folder name is the crate id.
+ *
+ * Can't apply check if the path is validated or not because it need pass through the
+ * parcel.
+ *
+ */
+ @Nullable
+ private String mId;
+
+ private CrateInfo() {
+ mExpiration = 0;
+ }
+
+ /**
+ * To create the crateInfo by passing validated label.
+ * @param label a display name for the crate
+ * @param expiration It's positive integer. if current time is larger than the expiration, the
+ * files under this crate will be considered to be deleted. Default value is 0.
+ * @throws IllegalArgumentException cause IllegalArgumentException when label is null
+ * or empty string
+ */
+ public CrateInfo(@NonNull CharSequence label, @CurrentTimeMillisLong long expiration) {
+ Preconditions.checkStringNotEmpty(label,
+ "Label should not be either null or empty string");
+ Preconditions.checkArgumentNonnegative(expiration,
+ "Expiration should be non negative number");
+
+ mLabel = label;
+ mExpiration = expiration;
+ }
+
+ /**
+ * To create the crateInfo by passing validated label.
+ * @param label a display name for the crate
+ * @throws IllegalArgumentException cause IllegalArgumentException when label is null
+ * or empty string
+ */
+ public CrateInfo(@NonNull CharSequence label) {
+ this(label, 0);
+ }
+
+ /**
+ * To get the meaningful text of the crate for the users.
+ * @return the meaningful text
+ */
+ @NonNull
+ public CharSequence getLabel() {
+ if (TextUtils.isEmpty(mLabel)) {
+ return mId;
+ }
+ return mLabel;
+ }
+
+
+ /**
+ * To return the expiration time.
+ *
+ * If the current time is larger than expiration time, the crate files are considered to be
+ * deleted.
+ *
+ * @return the expiration time
+ */
+ @CurrentTimeMillisLong
+ public long getExpirationMillis() {
+ return mExpiration;
+ }
+
+ /**
+ * To set the expiration time.
+ * @param expiration the expiration time
+ * @hide
+ */
+ public void setExpiration(@CurrentTimeMillisLong long expiration) {
+ Preconditions.checkArgumentNonnegative(expiration);
+ mExpiration = expiration;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ /**
+ * To compare with crateinfo when selves' mId is validated.
+ * The validated crateinfo.mId must be validated the following items.
+ *
+ * - mId is not null
+ * - mId is not empty string
+ *
+ *
+ * @param obj the reference object with which to compare.
+ * @return true when selves's mId is validated and equal to crateinfo.mId.
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj instanceof CrateInfo) {
+ CrateInfo crateInfo = (CrateInfo) obj;
+ if (!TextUtils.isEmpty(mId)
+ && TextUtils.equals(mId, crateInfo.mId)) {
+ return true;
+ }
+ }
+
+ return super.equals(obj);
+ }
+
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@Nullable Parcel dest, int flags) {
+ if (dest == null) {
+ return;
+ }
+
+ dest.writeCharSequence(mLabel);
+ dest.writeLong(mExpiration);
+
+ dest.writeInt(mUid);
+ dest.writeString(mPackageName);
+ dest.writeString(mId);
+ }
+
+ /**
+ * To read the data from parcel.
+ *
+ * It's called by StorageStatsService.
+ *
+ * @hide
+ */
+ public void readFromParcel(@Nullable Parcel in) {
+ if (in == null) {
+ return;
+ }
+
+ mLabel = in.readCharSequence();
+ mExpiration = in.readLong();
+
+ mUid = in.readInt();
+ mPackageName = in.readString();
+ mId = in.readString();
+ }
+
+ @NonNull
+ public static final Creator CREATOR = new Creator<>() {
+ @NonNull
+ @Override
+ public CrateInfo createFromParcel(@NonNull Parcel in) {
+ CrateInfo crateInfo = new CrateInfo();
+ crateInfo.readFromParcel(in);
+ return crateInfo;
+ }
+
+ @NonNull
+ @Override
+ public CrateInfo[] newArray(int size) {
+ return new CrateInfo[size];
+ }
+ };
+
+ /**
+ * To copy the information from service into crateinfo.
+ *
+ * This function is called in system_server. The copied information includes
+ *
+ * - uid
+ * - package name
+ * - crate id
+ *
+ *
+ * @param uid the uid that the crate belong to
+ * @param packageName the package name that the crate belong to
+ * @param id the crate dir
+ * @return the CrateInfo instance
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public static CrateInfo copyFrom(int uid, @Nullable String packageName, @Nullable String id) {
+ if (!UserHandle.isApp(uid) || TextUtils.isEmpty(packageName) || TextUtils.isEmpty(id)) {
+ return null;
+ }
+
+ CrateInfo crateInfo = new CrateInfo(id /* default label = id */, 0);
+ crateInfo.mUid = uid;
+ crateInfo.mPackageName = packageName;
+ crateInfo.mId = id;
+ return crateInfo;
+ }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cb5b4a595ac3d..449054bb5f105 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2201,6 +2201,17 @@
+
+
+
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 51bf441fe119b..aefdce48d9893 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -227,6 +227,9 @@
+
+
+
convertCrateInfoFrom(@Nullable CrateMetadata[] crateMetadatas) {
+ if (ArrayUtils.isEmpty(crateMetadatas)) {
+ return Collections.EMPTY_LIST;
+ }
+
+ ArrayList crateInfos = new ArrayList<>();
+ for (CrateMetadata crateMetadata : crateMetadatas) {
+ if (crateMetadata == null || TextUtils.isEmpty(crateMetadata.id)
+ || TextUtils.isEmpty(crateMetadata.packageName)) {
+ continue;
+ }
+
+ CrateInfo crateInfo = CrateInfo.copyFrom(crateMetadata.uid,
+ crateMetadata.packageName, crateMetadata.id);
+ if (crateInfo == null) {
+ continue;
+ }
+
+ crateInfos.add(crateInfo);
+ }
+
+ return crateInfos;
+ }
+
+ @NonNull
+ private ParceledListSlice getAppCrates(String volumeUuid, String[] packageNames,
+ @UserIdInt int userId) {
+ try {
+ CrateMetadata[] crateMetadatas = mInstaller.getAppCrates(volumeUuid,
+ packageNames, userId);
+ return new ParceledListSlice<>(convertCrateInfoFrom(crateMetadatas));
+ } catch (InstallerException e) {
+ throw new ParcelableException(new IOException(e.getMessage()));
+ }
+ }
+
+ @NonNull
+ @Override
+ public ParceledListSlice queryCratesForPackage(String volumeUuid,
+ @NonNull String packageName, @UserIdInt int userId, @NonNull String callingPackage) {
+ if (userId != UserHandle.getCallingUserId()) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
+ }
+
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = mPackage.getApplicationInfoAsUser(packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+ } catch (NameNotFoundException e) {
+ throw new ParcelableException(e);
+ }
+
+ if (Binder.getCallingUid() == appInfo.uid) {
+ // No permissions required when asking about themselves
+ } else {
+ enforceCratesPermission(Binder.getCallingUid(), callingPackage);
+ }
+
+ final String[] packageNames = new String[] { packageName };
+ return getAppCrates(volumeUuid, packageNames, userId);
+ }
+
+ @NonNull
+ @Override
+ public ParceledListSlice queryCratesForUid(String volumeUuid, int uid,
+ @NonNull String callingPackage) {
+ final int userId = UserHandle.getUserId(uid);
+ if (userId != UserHandle.getCallingUserId()) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
+ }
+
+ if (Binder.getCallingUid() == uid) {
+ // No permissions required when asking about themselves
+ } else {
+ enforceCratesPermission(Binder.getCallingUid(), callingPackage);
+ }
+
+ final String[] packageNames = defeatNullable(mPackage.getPackagesForUid(uid));
+ String[] validatedPackageNames = new String[0];
+
+ for (String packageName : packageNames) {
+ if (TextUtils.isEmpty(packageName)) {
+ continue;
+ }
+
+ try {
+ final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+ if (appInfo == null) {
+ continue;
+ }
+
+ validatedPackageNames = ArrayUtils.appendElement(String.class,
+ validatedPackageNames, packageName);
+ } catch (NameNotFoundException e) {
+ throw new ParcelableException(e);
+ }
+ }
+
+ return getAppCrates(volumeUuid, validatedPackageNames, userId);
+ }
+
+ @NonNull
+ @Override
+ public ParceledListSlice queryCratesForUser(String volumeUuid, int userId,
+ @NonNull String callingPackage) {
+ if (userId != UserHandle.getCallingUserId()) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
+ }
+
+ // Always require permission to see user-level stats
+ enforceCratesPermission(Binder.getCallingUid(), callingPackage);
+
+ try {
+ CrateMetadata[] crateMetadatas = mInstaller.getUserCrates(volumeUuid, userId);
+ return new ParceledListSlice<>(convertCrateInfoFrom(crateMetadatas));
+ } catch (InstallerException e) {
+ throw new ParcelableException(new IOException(e.getMessage()));
+ }
+ }
}