Merge "More APIs for cache status and behavior."
This commit is contained in:
committed by
Android (Google) Code Review
commit
bcde3dce83
@@ -30466,14 +30466,22 @@ package android.os.storage {
|
||||
}
|
||||
|
||||
public class StorageManager {
|
||||
method public long getCacheQuotaBytes();
|
||||
method public long getCacheSizeBytes();
|
||||
method public long getExternalCacheQuotaBytes();
|
||||
method public long getExternalCacheSizeBytes();
|
||||
method public java.lang.String getMountedObbPath(java.lang.String);
|
||||
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
|
||||
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
|
||||
method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
|
||||
method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException;
|
||||
method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException;
|
||||
method public boolean isEncrypted(java.io.File);
|
||||
method public boolean isObbMounted(java.lang.String);
|
||||
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
|
||||
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
|
||||
method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException;
|
||||
method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
|
||||
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
|
||||
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
|
||||
}
|
||||
|
||||
@@ -33220,14 +33220,22 @@ package android.os.storage {
|
||||
}
|
||||
|
||||
public class StorageManager {
|
||||
method public long getCacheQuotaBytes();
|
||||
method public long getCacheSizeBytes();
|
||||
method public long getExternalCacheQuotaBytes();
|
||||
method public long getExternalCacheSizeBytes();
|
||||
method public java.lang.String getMountedObbPath(java.lang.String);
|
||||
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
|
||||
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
|
||||
method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
|
||||
method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException;
|
||||
method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException;
|
||||
method public boolean isEncrypted(java.io.File);
|
||||
method public boolean isObbMounted(java.lang.String);
|
||||
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
|
||||
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
|
||||
method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException;
|
||||
method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
|
||||
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
|
||||
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
|
||||
}
|
||||
|
||||
@@ -30579,14 +30579,22 @@ package android.os.storage {
|
||||
}
|
||||
|
||||
public class StorageManager {
|
||||
method public long getCacheQuotaBytes();
|
||||
method public long getCacheSizeBytes();
|
||||
method public long getExternalCacheQuotaBytes();
|
||||
method public long getExternalCacheSizeBytes();
|
||||
method public java.lang.String getMountedObbPath(java.lang.String);
|
||||
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
|
||||
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
|
||||
method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
|
||||
method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException;
|
||||
method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException;
|
||||
method public boolean isEncrypted(java.io.File);
|
||||
method public boolean isObbMounted(java.lang.String);
|
||||
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
|
||||
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
|
||||
method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException;
|
||||
method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
|
||||
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
|
||||
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
|
||||
}
|
||||
|
||||
@@ -291,4 +291,6 @@ interface IStorageManager {
|
||||
void fstrim(int flags) = 72;
|
||||
AppFuseMount mountProxyFileDescriptorBridge() = 73;
|
||||
ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
|
||||
long getCacheQuotaBytes(String volumeUuid, int uid) = 75;
|
||||
long getCacheSizeBytes(String volumeUuid, int uid) = 76;
|
||||
}
|
||||
|
||||
@@ -24,27 +24,32 @@ import android.annotation.SdkConstant;
|
||||
import android.app.ActivityThread;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageMoveObserver;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Environment;
|
||||
import android.os.FileUtils;
|
||||
import android.os.Handler;
|
||||
import android.os.ProxyFileDescriptorCallback;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.ProxyFileDescriptorCallback;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ServiceManager.ServiceNotFoundException;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.os.AppFuseMount;
|
||||
@@ -60,6 +65,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -1396,6 +1402,222 @@ public class StorageManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return quota size in bytes for cached data belonging to the calling app.
|
||||
* <p>
|
||||
* If your app goes above this quota, your cached files will be some of the
|
||||
* first to be deleted when additional disk space is needed. Conversely, if
|
||||
* your app stays under this quota, your cached files will be some of the
|
||||
* last to be deleted when additional disk space is needed.
|
||||
* <p>
|
||||
* This quota may change over time depending on how frequently the user
|
||||
* interacts with your app, and depending on how much disk space is used.
|
||||
* <p>
|
||||
* Cached data tracked by this method always includes
|
||||
* {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
|
||||
* it also includes {@link Context#getExternalCacheDir()} if the primary
|
||||
* shared/external storage is hosted on the same storage device as your
|
||||
* private data.
|
||||
* <p class="note">
|
||||
* Note: if your app uses the {@code android:sharedUserId} manifest feature,
|
||||
* then cached data for all packages in your shared UID is tracked together
|
||||
* as a single unit.
|
||||
* </p>
|
||||
*
|
||||
* @see #getCacheQuotaBytes()
|
||||
* @see #getCacheSizeBytes()
|
||||
* @see #getExternalCacheQuotaBytes()
|
||||
* @see #getExternalCacheSizeBytes()
|
||||
*/
|
||||
public long getCacheQuotaBytes() {
|
||||
try {
|
||||
final ApplicationInfo app = mContext.getApplicationInfo();
|
||||
return mStorageManager.getCacheQuotaBytes(app.volumeUuid, app.uid);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return total size in bytes of cached data belonging to the calling app.
|
||||
* <p>
|
||||
* Cached data tracked by this method always includes
|
||||
* {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
|
||||
* it also includes {@link Context#getExternalCacheDir()} if the primary
|
||||
* shared/external storage is hosted on the same storage device as your
|
||||
* private data.
|
||||
* <p class="note">
|
||||
* Note: if your app uses the {@code android:sharedUserId} manifest feature,
|
||||
* then cached data for all packages in your shared UID is tracked together
|
||||
* as a single unit.
|
||||
* </p>
|
||||
*
|
||||
* @see #getCacheQuotaBytes()
|
||||
* @see #getCacheSizeBytes()
|
||||
* @see #getExternalCacheQuotaBytes()
|
||||
* @see #getExternalCacheSizeBytes()
|
||||
*/
|
||||
public long getCacheSizeBytes() {
|
||||
try {
|
||||
final ApplicationInfo app = mContext.getApplicationInfo();
|
||||
return mStorageManager.getCacheSizeBytes(app.volumeUuid, app.uid);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return quota size in bytes for cached data on primary shared/external
|
||||
* storage belonging to the calling app.
|
||||
* <p>
|
||||
* If primary shared/external storage is hosted on the same storage device
|
||||
* as your private data, this method will return -1, since all data stored
|
||||
* under {@link Context#getExternalCacheDir()} will be counted under
|
||||
* {@link #getCacheQuotaBytes()}.
|
||||
* <p class="note">
|
||||
* Note: if your app uses the {@code android:sharedUserId} manifest feature,
|
||||
* then cached data for all packages in your shared UID is tracked together
|
||||
* as a single unit.
|
||||
* </p>
|
||||
*/
|
||||
public long getExternalCacheQuotaBytes() {
|
||||
final ApplicationInfo app = mContext.getApplicationInfo();
|
||||
final String primaryUuid = getPrimaryStorageUuid();
|
||||
if (Objects.equals(app.volumeUuid, primaryUuid)) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
return mStorageManager.getCacheQuotaBytes(primaryUuid, app.uid);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return total size in bytes of cached data on primary shared/external
|
||||
* storage belonging to the calling app.
|
||||
* <p>
|
||||
* If primary shared/external storage is hosted on the same storage device
|
||||
* as your private data, this method will return -1, since all data stored
|
||||
* under {@link Context#getExternalCacheDir()} will be counted under
|
||||
* {@link #getCacheQuotaBytes()}.
|
||||
* <p class="note">
|
||||
* Note: if your app uses the {@code android:sharedUserId} manifest feature,
|
||||
* then cached data for all packages in your shared UID is tracked together
|
||||
* as a single unit.
|
||||
* </p>
|
||||
*/
|
||||
public long getExternalCacheSizeBytes() {
|
||||
final ApplicationInfo app = mContext.getApplicationInfo();
|
||||
final String primaryUuid = getPrimaryStorageUuid();
|
||||
if (Objects.equals(app.volumeUuid, primaryUuid)) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
return mStorageManager.getCacheSizeBytes(primaryUuid, app.uid);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
private static final String XATTR_ATOMIC = "user.atomic";
|
||||
private static final String XATTR_TOMBSTONE = "user.tombstone";
|
||||
|
||||
/** {@hide} */
|
||||
private static void setCacheBehavior(File path, String name, boolean enabled)
|
||||
throws IOException {
|
||||
if (!path.isDirectory()) {
|
||||
throw new IOException("Cache behavior can only be set on directories");
|
||||
}
|
||||
if (enabled) {
|
||||
try {
|
||||
Os.setxattr(path.getAbsolutePath(), name,
|
||||
"1".getBytes(StandardCharsets.UTF_8), 0);
|
||||
} catch (ErrnoException e) {
|
||||
throw e.rethrowAsIOException();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Os.removexattr(path.getAbsolutePath(), name);
|
||||
} catch (ErrnoException e) {
|
||||
if (e.errno != OsConstants.ENODATA) {
|
||||
throw e.rethrowAsIOException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
private static boolean isCacheBehavior(File path, String name) throws IOException {
|
||||
try {
|
||||
Os.getxattr(path.getAbsolutePath(), name);
|
||||
return true;
|
||||
} catch (ErrnoException e) {
|
||||
if (e.errno != OsConstants.ENODATA) {
|
||||
throw e.rethrowAsIOException();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable special cache behavior that treats this directory and
|
||||
* its contents as an atomic unit.
|
||||
* <p>
|
||||
* When enabled and this directory is considered for automatic deletion by
|
||||
* the OS, all contained files will either be deleted together, or not at
|
||||
* all. This is useful when you have a directory that contains several
|
||||
* related metadata files that depend on each other, such as movie file and
|
||||
* a subtitle file.
|
||||
* <p>
|
||||
* When enabled, the <em>newest</em> {@link File#lastModified()} value of
|
||||
* any contained files is considered the modified time of the entire
|
||||
* directory.
|
||||
* <p>
|
||||
* This behavior can only be set on a directory, and it applies recursively
|
||||
* to all contained files and directories.
|
||||
*/
|
||||
public void setCacheBehaviorAtomic(File path, boolean atomic) throws IOException {
|
||||
setCacheBehavior(path, XATTR_ATOMIC, atomic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the current value set by
|
||||
* {@link #setCacheBehaviorAtomic(File, boolean)}.
|
||||
*/
|
||||
public boolean isCacheBehaviorAtomic(File path) throws IOException {
|
||||
return isCacheBehavior(path, XATTR_ATOMIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable special cache behavior that leaves deleted cache files
|
||||
* intact as tombstones.
|
||||
* <p>
|
||||
* When enabled and a file contained in this directory is automatically
|
||||
* deleted by the OS, the file will be truncated to have a length of 0 bytes
|
||||
* instead of being fully deleted. This is useful if you need to distinguish
|
||||
* between a file that was deleted versus one that never existed.
|
||||
* <p>
|
||||
* This behavior can only be set on a directory, and it applies recursively
|
||||
* to all contained files and directories.
|
||||
* <p class="note">
|
||||
* Note: this behavior is ignored completely if the user explicitly requests
|
||||
* that all cached data be cleared.
|
||||
* </p>
|
||||
*/
|
||||
public void setCacheBehaviorTombstone(File path, boolean tombstone) throws IOException {
|
||||
setCacheBehavior(path, XATTR_TOMBSTONE, tombstone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the current value set by
|
||||
* {@link #setCacheBehaviorTombstone(File, boolean)}.
|
||||
*/
|
||||
public boolean isCacheBehaviorTombstone(File path) throws IOException {
|
||||
return isCacheBehavior(path, XATTR_TOMBSTONE);
|
||||
}
|
||||
|
||||
private final Object mFuseAppLoopLock = new Object();
|
||||
|
||||
@GuardedBy("mFuseAppLoopLock")
|
||||
|
||||
@@ -33,6 +33,7 @@ import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.usage.StorageStatsManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -3270,6 +3271,29 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCacheQuotaBytes(String volumeUuid, int uid) {
|
||||
if (uid != Binder.getCallingUid()) {
|
||||
mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG);
|
||||
}
|
||||
// TODO: wire up to cache quota once merged
|
||||
return 64 * TrafficStats.MB_IN_BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCacheSizeBytes(String volumeUuid, int uid) {
|
||||
if (uid != Binder.getCallingUid()) {
|
||||
mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG);
|
||||
}
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mContext.getSystemService(StorageStatsManager.class)
|
||||
.queryStatsForUid(volumeUuid, uid).getCacheBytes();
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
private void addObbStateLocked(ObbState obbState) throws RemoteException {
|
||||
final IBinder binder = obbState.getBinder();
|
||||
List<ObbState> obbStates = mObbMounts.get(binder);
|
||||
|
||||
@@ -37,6 +37,7 @@ import android.os.storage.VolumeInfo;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.pm.Installer;
|
||||
import com.android.server.pm.Installer.InstallerException;
|
||||
@@ -46,8 +47,6 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
|
||||
|
||||
private static final String PROP_VERIFY_STORAGE = "fw.verify_storage";
|
||||
|
||||
// TODO: pivot all methods to manual mode when quota isn't supported
|
||||
|
||||
public static class Lifecycle extends SystemService {
|
||||
private StorageStatsService mService;
|
||||
|
||||
@@ -71,11 +70,11 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
|
||||
private final Installer mInstaller;
|
||||
|
||||
public StorageStatsService(Context context) {
|
||||
mContext = context;
|
||||
mAppOps = context.getSystemService(AppOpsManager.class);
|
||||
mUser = context.getSystemService(UserManager.class);
|
||||
mPackage = context.getSystemService(PackageManager.class);
|
||||
mStorage = context.getSystemService(StorageManager.class);
|
||||
mContext = Preconditions.checkNotNull(context);
|
||||
mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class));
|
||||
mUser = Preconditions.checkNotNull(context.getSystemService(UserManager.class));
|
||||
mPackage = Preconditions.checkNotNull(context.getPackageManager());
|
||||
mStorage = Preconditions.checkNotNull(context.getSystemService(StorageManager.class));
|
||||
|
||||
mInstaller = new Installer(context);
|
||||
mInstaller.onStart();
|
||||
@@ -107,7 +106,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
|
||||
case AppOpsManager.MODE_ALLOWED:
|
||||
return;
|
||||
case AppOpsManager.MODE_DEFAULT:
|
||||
mContext.enforceCallingPermission(
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.PACKAGE_USAGE_STATS, TAG);
|
||||
return;
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user