Merge "More APIs for cache status and behavior."

This commit is contained in:
TreeHugger Robot
2017-01-24 05:32:56 +00:00
committed by Android (Google) Code Review
7 changed files with 280 additions and 9 deletions

View File

@@ -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";
}

View File

@@ -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";
}

View File

@@ -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";
}

View File

@@ -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;
}

View File

@@ -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")

View File

@@ -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);

View File

@@ -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: