diff options
author | Jeff Sharkey <jsharkey@android.com> | 2017-06-09 10:01:20 -0600 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2017-06-12 14:27:23 -0600 |
commit | 35e46d297255363a20ccde62af3c58c4ce3c13c5 (patch) | |
tree | 6a082247418b5cc02d46e4a351a4d3f930ce779c | |
parent | 9620595965bc7ffb74518eee19bbd63702c57121 (diff) |
Active camera apps can defy reserved cache space.
We normally prevent apps from allocating into the "reserved" cache
space, but this change makes an exception for an active camera app,
since the user is probably trying to capture an important memory.
This change only lets the active camera app clear up to half of the
reserved space, since we don't want to completely destroy the
experience of all other apps.
Test: manual app before/during/after active camera session
Bug: 38267830
Change-Id: Ie9e63884fb2638ca881e10b894629eea84601648
10 files changed, 107 insertions, 44 deletions
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index d71573f7ca50..c4193f647c32 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -1473,7 +1473,7 @@ public final class Pm { ClearDataObserver obs = new ClearDataObserver(); try { mPm.freeStorageAndNotify(volumeUuid, sizeVal, - StorageManager.FLAG_ALLOCATE_DEFY_RESERVED, obs); + StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs); synchronized (obs) { while (!obs.finished) { try { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index e672ada3cbb4..b331d84010d0 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1947,4 +1947,13 @@ public class AppOpsManager { public void finishOp(int op) { finishOp(op, Process.myUid(), mContext.getOpPackageName()); } + + /** @hide */ + public boolean isOperationActive(int code, int uid, String packageName) { + try { + return mService.isOperationActive(code, uid, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 92f7f319a14b..50855bb349d9 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -293,7 +293,7 @@ interface IStorageManager { ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74; long getCacheQuotaBytes(String volumeUuid, int uid) = 75; long getCacheSizeBytes(String volumeUuid, int uid) = 76; - long getAllocatableBytes(String volumeUuid, int flags) = 77; - void allocateBytes(String volumeUuid, long bytes, int flags) = 78; + long getAllocatableBytes(String volumeUuid, int flags, String callingPackage) = 77; + void allocateBytes(String volumeUuid, long bytes, int flags, String callingPackage) = 78; void secdiscard(in String path) = 79; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 504673529238..ca7c73903481 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1174,7 +1174,7 @@ public class StorageManager { * * @hide */ - public long getStorageCacheBytes(File path) { + public long getStorageCacheBytes(File path, @AllocateFlags int flags) { final long cachePercent = Settings.Global.getInt(mResolver, Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE, DEFAULT_CACHE_PERCENTAGE); final long cacheBytes = (path.getTotalSpace() * cachePercent) / 100; @@ -1182,7 +1182,16 @@ public class StorageManager { final long maxCacheBytes = Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES, DEFAULT_CACHE_MAX_BYTES); - return Math.min(cacheBytes, maxCacheBytes); + final long result = Math.min(cacheBytes, maxCacheBytes); + if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) { + return 0; + } else if ((flags & StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED) != 0) { + return 0; + } else if ((flags & StorageManager.FLAG_ALLOCATE_DEFY_HALF_RESERVED) != 0) { + return result / 2; + } else { + return result; + } } /** @@ -1628,17 +1637,26 @@ public class StorageManager { public static final int FLAG_ALLOCATE_AGGRESSIVE = 1 << 0; /** - * Flag indicating that a disk space allocation request should defy any - * reserved disk space. + * Flag indicating that a disk space allocation request should be allowed to + * clear up to all reserved disk space. + * + * @hide + */ + public static final int FLAG_ALLOCATE_DEFY_ALL_RESERVED = 1 << 1; + + /** + * Flag indicating that a disk space allocation request should be allowed to + * clear up to half of all reserved disk space. * * @hide */ - public static final int FLAG_ALLOCATE_DEFY_RESERVED = 1 << 1; + public static final int FLAG_ALLOCATE_DEFY_HALF_RESERVED = 1 << 2; /** @hide */ @IntDef(flag = true, value = { FLAG_ALLOCATE_AGGRESSIVE, - FLAG_ALLOCATE_DEFY_RESERVED, + FLAG_ALLOCATE_DEFY_ALL_RESERVED, + FLAG_ALLOCATE_DEFY_HALF_RESERVED, }) @Retention(RetentionPolicy.SOURCE) public @interface AllocateFlags {} @@ -1688,7 +1706,8 @@ public class StorageManager { public long getAllocatableBytes(@NonNull UUID storageUuid, @RequiresPermission @AllocateFlags int flags) throws IOException { try { - return mStorageManager.getAllocatableBytes(convert(storageUuid), flags); + return mStorageManager.getAllocatableBytes(convert(storageUuid), flags, + mContext.getOpPackageName()); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); @@ -1738,7 +1757,8 @@ public class StorageManager { public void allocateBytes(@NonNull UUID storageUuid, @BytesLong long bytes, @RequiresPermission @AllocateFlags int flags) throws IOException { try { - mStorageManager.allocateBytes(convert(storageUuid), bytes, flags); + mStorageManager.allocateBytes(convert(storageUuid), bytes, flags, + mContext.getOpPackageName()); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); } catch (RemoteException e) { diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 35d4ba81bd69..7a119b4351c0 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -48,4 +48,6 @@ interface IAppOpsService { void setUserRestrictions(in Bundle restrictions, IBinder token, int userHandle); void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, in String[] exceptionPackages); void removeUser(int userHandle); + + boolean isOperationActive(int code, int uid, String packageName); } diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index 1a5ec61a1510..c8e6e2efb240 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -2445,6 +2445,28 @@ public class AppOpsService extends IAppOpsService.Stub { } } + @Override + public boolean isOperationActive(int code, int uid, String packageName) { + verifyIncomingUid(uid); + verifyIncomingOp(code); + String resolvedPackageName = resolvePackageName(uid, packageName); + if (resolvedPackageName == null) { + return false; + } + synchronized (this) { + for (int i = mClients.size() - 1; i >= 0; i--) { + final ClientState client = mClients.valueAt(i); + if (client.mStartedOps == null) continue; + + for (int j = client.mStartedOps.size() - 1; j >= 0; j--) { + final Op op = client.mStartedOps.get(j); + if (op.op == code && op.uid == uid) return true; + } + } + } + return false; + } + private void removeUidsForUserLocked(int userHandle) { for (int i = mUidStates.size() - 1; i >= 0; --i) { final int uid = mUidStates.keyAt(i); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 35b452a12e64..f718e803a973 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -90,14 +90,12 @@ import android.util.AtomicFile; import android.util.Log; import android.util.Pair; import android.util.Slog; -import android.util.SparseArray; import android.util.TimeUtils; import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IMediaContainerService; import com.android.internal.os.AppFuseMount; -import com.android.internal.os.FuseAppLoop; import com.android.internal.os.FuseUnavailableMountException; import com.android.internal.os.SomeArgs; import com.android.internal.os.Zygote; @@ -143,7 +141,6 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; -import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -3291,20 +3288,41 @@ class StorageManagerService extends IStorageManager.Stub } } - @Override - public long getAllocatableBytes(String volumeUuid, int flags) { - final StorageManager storage = mContext.getSystemService(StorageManager.class); - final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class); - - // Apps can't defy reserved space - flags &= ~StorageManager.FLAG_ALLOCATE_DEFY_RESERVED; - - final boolean aggressive = (flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0; - if (aggressive) { + private int adjustAllocateFlags(int flags, int callingUid, String callingPackage) { + // Require permission to allocate aggressively + if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ALLOCATE_AGGRESSIVE, TAG); } + // Apps normally can't directly defy reserved space + flags &= ~StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED; + flags &= ~StorageManager.FLAG_ALLOCATE_DEFY_HALF_RESERVED; + + // However, if app is actively using the camera, then we're willing to + // clear up to half of the reserved cache space, since the user might be + // trying to capture an important memory. + final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); + final long token = Binder.clearCallingIdentity(); + try { + if (appOps.isOperationActive(AppOpsManager.OP_CAMERA, callingUid, callingPackage)) { + Slog.d(TAG, "UID " + callingUid + " is actively using camera;" + + " letting them defy reserved cached data"); + flags |= StorageManager.FLAG_ALLOCATE_DEFY_HALF_RESERVED; + } + } finally { + Binder.restoreCallingIdentity(token); + } + + return flags; + } + + @Override + public long getAllocatableBytes(String volumeUuid, int flags, String callingPackage) { + flags = adjustAllocateFlags(flags, Binder.getCallingUid(), callingPackage); + + final StorageManager storage = mContext.getSystemService(StorageManager.class); + final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class); final long token = Binder.clearCallingIdentity(); try { // In general, apps can allocate as much space as they want, except @@ -3319,18 +3337,18 @@ class StorageManagerService extends IStorageManager.Stub if (stats.isQuotaSupported(volumeUuid)) { final long cacheTotal = stats.getCacheBytes(volumeUuid); - final long cacheReserved = storage.getStorageCacheBytes(path); + final long cacheReserved = storage.getStorageCacheBytes(path, flags); final long cacheClearable = Math.max(0, cacheTotal - cacheReserved); - if (aggressive) { - return Math.max(0, (usable + cacheTotal) - fullReserved); + if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) { + return Math.max(0, (usable + cacheClearable) - fullReserved); } else { return Math.max(0, (usable + cacheClearable) - lowReserved); } } else { // When we don't have fast quota information, we ignore cached // data and only consider unused bytes. - if (aggressive) { + if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) { return Math.max(0, usable - fullReserved); } else { return Math.max(0, usable - lowReserved); @@ -3344,20 +3362,16 @@ class StorageManagerService extends IStorageManager.Stub } @Override - public void allocateBytes(String volumeUuid, long bytes, int flags) { - final StorageManager storage = mContext.getSystemService(StorageManager.class); + public void allocateBytes(String volumeUuid, long bytes, int flags, String callingPackage) { + flags = adjustAllocateFlags(flags, Binder.getCallingUid(), callingPackage); - // Apps can't defy reserved space - flags &= ~StorageManager.FLAG_ALLOCATE_DEFY_RESERVED; - - // This method call will enforce FLAG_ALLOCATE_AGGRESSIVE permissions so - // we don't have to enforce them locally - final long allocatableBytes = getAllocatableBytes(volumeUuid, flags); + final long allocatableBytes = getAllocatableBytes(volumeUuid, flags, callingPackage); if (bytes > allocatableBytes) { throw new ParcelableException(new IOException("Failed to allocate " + bytes + " because only " + allocatableBytes + " allocatable")); } + final StorageManager storage = mContext.getSystemService(StorageManager.class); final long token = Binder.clearCallingIdentity(); try { // Free up enough disk space to satisfy both the requested allocation diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 48b9c7de650b..9c04801c056b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4258,10 +4258,7 @@ public class PackageManagerService extends IPackageManager.Stub volumeUuid); final boolean aggressive = (storageFlags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0; - final boolean defyReserved = (storageFlags - & StorageManager.FLAG_ALLOCATE_DEFY_RESERVED) != 0; - final long reservedBytes = (aggressive || defyReserved) ? 0 - : storage.getStorageCacheBytes(file); + final long reservedBytes = storage.getStorageCacheBytes(file, storageFlags); // 1. Pre-flight to determine if we have any chance to succeed // 2. Consider preloaded data (after 1w honeymoon, unless aggressive) diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockStorageManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockStorageManager.java index 89e18b4c2f41..40e114bfaef2 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/MockStorageManager.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/MockStorageManager.java @@ -491,12 +491,12 @@ public class MockStorageManager implements IStorageManager { } @Override - public long getAllocatableBytes(String path, int flags) { + public long getAllocatableBytes(String path, int flags, String callingPackage) { throw new UnsupportedOperationException(); } @Override - public void allocateBytes(String path, long bytes, int flags) { + public void allocateBytes(String path, long bytes, int flags, String callingPackage) { throw new UnsupportedOperationException(); } @@ -504,5 +504,4 @@ public class MockStorageManager implements IStorageManager { public void secdiscard(String path) throws RemoteException { throw new UnsupportedOperationException(); } - } diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 562443f53546..9f4fb85f64c4 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -197,7 +197,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { // logic should be kept in sync with getAllocatableBytes(). if (isQuotaSupported(volumeUuid, callingPackage)) { final long cacheTotal = getCacheBytes(volumeUuid, callingPackage); - final long cacheReserved = mStorage.getStorageCacheBytes(path); + final long cacheReserved = mStorage.getStorageCacheBytes(path, 0); final long cacheClearable = Math.max(0, cacheTotal - cacheReserved); return path.getUsableSpace() + cacheClearable; |