diff --git a/api/system-current.txt b/api/system-current.txt index 8296eee381675..b75051f27fc9b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -304,7 +304,7 @@ package android.app { } public class AppOpsManager { - method @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public void getHistoricalOps(int, @Nullable String, @Nullable String[], long, long, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method public static String[] getOpStrs(); method @Deprecated @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List getOpsForPackage(int, String, int[]); method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List getOpsForPackage(int, @NonNull String, @Nullable java.lang.String...); @@ -394,6 +394,17 @@ package android.app { field public static final android.os.Parcelable.Creator CREATOR; } + public static final class AppOpsManager.HistoricalOpsRequest { + } + + public static final class AppOpsManager.HistoricalOpsRequest.Builder { + ctor public AppOpsManager.HistoricalOpsRequest.Builder(long, long); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build(); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setUid(int); + } + public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String); diff --git a/api/test-current.txt b/api/test-current.txt index 16098c1a80fab..a371a17de4a11 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -101,8 +101,8 @@ package android.app { public class AppOpsManager { method @RequiresPermission("android.permission.MANAGE_APPOPS") public void addHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOps); method @RequiresPermission("android.permission.MANAGE_APPOPS") public void clearHistory(); - method @RequiresPermission("android.permission.GET_APP_OPS_STATS") public void getHistoricalOps(int, @Nullable String, @Nullable String[], long, long, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); - method @RequiresPermission("android.permission.GET_APP_OPS_STATS") public void getHistoricalOpsFromDiskRaw(int, @Nullable String, @Nullable String[], long, long, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @RequiresPermission("android.permission.GET_APP_OPS_STATS") public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @RequiresPermission("android.permission.GET_APP_OPS_STATS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method public static int getNumOps(); method public static String[] getOpStrs(); method public boolean isOperationActive(int, int, String); @@ -206,6 +206,17 @@ package android.app { field public static final android.os.Parcelable.Creator CREATOR; } + public static final class AppOpsManager.HistoricalOpsRequest { + } + + public static final class AppOpsManager.HistoricalOpsRequest.Builder { + ctor public AppOpsManager.HistoricalOpsRequest.Builder(long, long); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build(); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setUid(int); + } + public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 13eec580ec148..040ad062e5121 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -46,6 +46,8 @@ import android.util.ArrayMap; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; + +import com.android.internal.annotations.Immutable; import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsNotedCallback; @@ -2209,6 +2211,115 @@ public class AppOpsManager { void visitHistoricalOp(@NonNull HistoricalOp ops); } + /** + * Request for getting historical app op usage. The request acts + * as a filtering criteria when querying historical op usage. + * + * @hide + */ + @Immutable + @TestApi + @SystemApi + public static final class HistoricalOpsRequest { + private final int mUid; + private final @Nullable String mPackageName; + private final @Nullable List mOpNames; + private final long mBeginTimeMillis; + private final long mEndTimeMillis; + + private HistoricalOpsRequest(int uid, @Nullable String packageName, + @Nullable List opNames, long beginTimeMillis, long endTimeMillis) { + mUid = uid; + mPackageName = packageName; + mOpNames = opNames; + mBeginTimeMillis = beginTimeMillis; + mEndTimeMillis = endTimeMillis; + } + + /** + * Builder for creating a {@link HistoricalOpsRequest}. + * + * @hide + */ + @TestApi + @SystemApi + public static final class Builder { + private int mUid = Process.INVALID_UID; + private @Nullable String mPackageName; + private @Nullable List mOpNames; + private final long mBeginTimeMillis; + private final long mEndTimeMillis; + + /** + * Creates a new builder. + * + * @param beginTimeMillis The beginning of the interval in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be non + * negative. + * @param endTimeMillis The end of the interval in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after + * {@code beginTimeMillis}. Pass {@link Long#MAX_VALUE} to get the most recent + * history including ops that happen while this call is in flight. + */ + public Builder(long beginTimeMillis, long endTimeMillis) { + Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis, + "beginTimeMillis must be non negative and lesser than endTimeMillis"); + mBeginTimeMillis = beginTimeMillis; + mEndTimeMillis = endTimeMillis; + } + + /** + * Sets the UID to query for. + * + * @param uid The uid. Pass {@link android.os.Process#INVALID_UID} for any uid. + * @return This builder. + */ + public @NonNull Builder setUid(int uid) { + Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0, + "uid must be " + Process.INVALID_UID + " or non negative"); + mUid = uid; + return this; + } + + /** + * Sets the package to query for. + * + * @param packageName The package name. Null for any package. + * @return This builder. + */ + public @NonNull Builder setPackageName(@Nullable String packageName) { + mPackageName = packageName; + return this; + } + + /** + * Sets the op names to query for. + * + * @param opNames The op names. Null for any op. + * @return This builder. + */ + public @NonNull Builder setOpNames(@Nullable List opNames) { + if (opNames != null) { + final int opCount = opNames.size(); + for (int i = 0; i < opCount; i++) { + Preconditions.checkArgument(AppOpsManager.strOpToOp( + opNames.get(i)) != AppOpsManager.OP_NONE); + } + } + mOpNames = opNames; + return this; + } + + /** + * @return a new {@link HistoricalOpsRequest}. + */ + public @NonNull HistoricalOpsRequest build() { + return new HistoricalOpsRequest(mUid, mPackageName, mOpNames, + mBeginTimeMillis, mEndTimeMillis); + } + } + } + /** * This class represents historical app op state of all UIDs for a given time interval. * @@ -3671,26 +3782,7 @@ public class AppOpsManager { /** * Retrieve historical app op stats for a period. * - *

Historical data can be obtained - * for a specific package by specifying the packageName argument, - * for a specific UID if specifying the uid argument, for a - * specific package in a UID by specifying the packageName - * and the uid arguments, for all packages by passing - * {@link android.os.Process#INVALID_UID} and null for the - * uid and packageName arguments, respectively. - * Similarly, you can specify the opNames argument to get - * data only for these ops or null for all ops. - * - * @param uid The UID to query for. - * @param packageName The package to query for. - * @param beginTimeMillis The beginning of the interval in milliseconds since - * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be non - * negative. - * @param endTimeMillis The end of the interval in milliseconds since - * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after - * {@code beginTimeMillis}. Pass {@link Long#MAX_VALUE} to get the most recent - * history including ops that happen while this call is in flight. - * @param opNames The ops to query for. Pass {@code null} for all ops. + * @param request A request object describing the data being queried for. * @param executor Executor on which to run the callback. If null * the callback is executed on the default executor running on the main thread. * @param callback Callback on which to deliver the result. @@ -3702,13 +3794,13 @@ public class AppOpsManager { @TestApi @SystemApi @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) - public void getHistoricalOps(int uid, @Nullable String packageName, - @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + public void getHistoricalOps(@NonNull HistoricalOpsRequest request, @NonNull Executor executor, @NonNull Consumer callback) { Preconditions.checkNotNull(executor, "executor cannot be null"); Preconditions.checkNotNull(callback, "callback cannot be null"); try { - mService.getHistoricalOps(uid, packageName, opNames, beginTimeMillis, endTimeMillis, + mService.getHistoricalOps(request.mUid, request.mPackageName, request.mOpNames, + request.mBeginTimeMillis, request.mEndTimeMillis, new RemoteCallback((result) -> { final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); final long identity = Binder.clearCallingIdentity(); @@ -3725,29 +3817,12 @@ public class AppOpsManager { /** * Retrieve historical app op stats for a period. - * - *

Historical data can be obtained - * for a specific package by specifying the packageName argument, - * for a specific UID if specifying the uid argument, for a - * specific package in a UID by specifying the packageName - * and the uid arguments, for all packages by passing - * {@link android.os.Process#INVALID_UID} and null for the - * uid and packageName arguments, respectively. - * Similarly, you can specify the opNames argument to get - * data only for these ops or null for all ops. *

* This method queries only the on disk state and the returned ops are raw, * which is their times are relative to the history start as opposed to the * epoch start. * - * @param uid The UID to query for. - * @param packageName The package to query for. - * @param beginTimeMillis The beginning of the interval in milliseconds since - * history start. History time grows as one goes into the past. - * @param endTimeMillis The end of the interval in milliseconds since - * history start. History time grows as one goes into the past. Must be after - * {@code beginTimeMillis}. - * @param opNames The ops to query for. Pass {@code null} for all ops. + * @param request A request object describing the data being queried for. * @param executor Executor on which to run the callback. If null * the callback is executed on the default executor running on the main thread. * @param callback Callback on which to deliver the result. @@ -3758,15 +3833,15 @@ public class AppOpsManager { */ @TestApi @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) - public void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName, - @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + public void getHistoricalOpsFromDiskRaw(@NonNull HistoricalOpsRequest request, @Nullable Executor executor, @NonNull Consumer callback) { Preconditions.checkNotNull(executor, "executor cannot be null"); Preconditions.checkNotNull(callback, "callback cannot be null"); try { - mService.getHistoricalOpsFromDiskRaw(uid, packageName, opNames, beginTimeMillis, - endTimeMillis, new RemoteCallback((result) -> { - final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); + mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName, + request.mOpNames, request.mBeginTimeMillis, request.mEndTimeMillis, + new RemoteCallback((result) -> { + final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); final long identity = Binder.clearCallingIdentity(); try { executor.execute(() -> callback.accept(ops)); diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index c4ab91f8b4292..8a90cade35a23 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -44,9 +44,9 @@ interface IAppOpsService { int checkPackage(int uid, String packageName); List getPackagesForOps(in int[] ops); List getOpsForPackage(int uid, String packageName, in int[] ops); - void getHistoricalOps(int uid, String packageName, in String[] ops, long beginTimeMillis, + void getHistoricalOps(int uid, String packageName, in List ops, long beginTimeMillis, long endTimeMillis, in RemoteCallback callback); - void getHistoricalOpsFromDiskRaw(int uid, String packageName, in String[] ops, + void getHistoricalOpsFromDiskRaw(int uid, String packageName, in List ops, long beginTimeMillis, long endTimeMillis, in RemoteCallback callback); void offsetHistory(long duration); void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep); diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 0e3309088f21e..70c28a854512c 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -38,6 +38,7 @@ import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AppOpsManager.HistoricalOps; +import android.app.AppOpsManager.HistoricalOpsRequest; import android.app.AppOpsManagerInternal; import android.app.AppOpsManagerInternal.CheckOpsDelegate; import android.content.BroadcastReceiver; @@ -1024,25 +1025,29 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void getHistoricalOps(int uid, @NonNull String packageName, - @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + @Nullable List opNames, long beginTimeMillis, long endTimeMillis, @NonNull RemoteCallback callback) { - Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0, - "uid must be " + Process.INVALID_UID + " or non negative"); - Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis, - "beginTimeMillis must be non negative and lesser than endTimeMillis"); + // Use the builder to validate arguments. + final HistoricalOpsRequest request = new HistoricalOpsRequest.Builder( + beginTimeMillis, endTimeMillis) + .setUid(uid) + .setPackageName(packageName) + .setOpNames(opNames) + .build(); Preconditions.checkNotNull(callback, "callback cannot be null"); - checkValidOpsOrNull(opNames); mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps"); + final String[] opNamesArray = (opNames != null) + ? opNames.toArray(new String[opNames.size()]) : null; if (mHistoricalRegistry.getMode() == AppOpsManager.HISTORICAL_MODE_DISABLED) { // TODO (bug:122218838): Remove once the feature fully enabled. - getHistoricalPackagesOpsCompat(uid, packageName, opNames, beginTimeMillis, + getHistoricalPackagesOpsCompat(uid, packageName, opNamesArray, beginTimeMillis, endTimeMillis, callback); } else { // Must not hold the appops lock - mHistoricalRegistry.getHistoricalOps(uid, packageName, opNames, + mHistoricalRegistry.getHistoricalOps(uid, packageName, opNamesArray, beginTimeMillis, endTimeMillis, callback); } } @@ -1101,20 +1106,25 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName, - @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + @Nullable List opNames, long beginTimeMillis, long endTimeMillis, @NonNull RemoteCallback callback) { - Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0, - "uid must be " + Process.INVALID_UID + " or non negative"); - Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis, - "beginTimeMillis must be non negative and lesser than endTimeMillis"); + // Use the builder to validate arguments. + final HistoricalOpsRequest request = new HistoricalOpsRequest.Builder( + beginTimeMillis, endTimeMillis) + .setUid(uid) + .setPackageName(packageName) + .setOpNames(opNames) + .build(); Preconditions.checkNotNull(callback, "callback cannot be null"); - checkValidOpsOrNull(opNames); mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps"); + final String[] opNamesArray = (opNames != null) + ? opNames.toArray(new String[opNames.size()]) : null; + // Must not hold the appops lock - mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, opNames, + mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, opNamesArray, beginTimeMillis, endTimeMillis, callback); } @@ -4266,16 +4276,6 @@ public class AppOpsService extends IAppOpsService.Stub { return packageNames; } - private static void checkValidOpsOrNull(String[] opNames) { - if (opNames != null) { - for (String opName : opNames) { - if (AppOpsManager.strOpToOp(opName) == AppOpsManager.OP_NONE) { - throw new IllegalArgumentException("Unknown op: " + opName); - } - } - } - } - private final class ClientRestrictionState implements DeathRecipient { private final IBinder token; SparseArray perUserRestrictions;