summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2021-06-02 23:07:22 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2021-06-02 23:07:22 +0000
commit1028eb2f62cc0fd4529039e6bc19d80d1759706b (patch)
tree4b7ce5b1fc2c7cb6a5c6dc316597d016910c6b2f /core/java/android
parent13b9214a174f81c51b6ac0727a405e502af912ab (diff)
parent2eebf929650e0d320a21f0d13677a27d7ab278e9 (diff)
Merge "Switch media fw permissions checks to AttributionSource" into sc-dev
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/app/AppOpsManager.java177
-rw-r--r--core/java/android/app/AppOpsManagerInternal.java40
-rw-r--r--core/java/android/app/ContextImpl.java8
-rw-r--r--core/java/android/app/SystemServiceRegistry.java9
-rw-r--r--core/java/android/content/AttributionSource.java131
-rw-r--r--core/java/android/content/ContentProvider.java13
-rw-r--r--core/java/android/content/Context.java8
-rw-r--r--core/java/android/content/PermissionChecker.java139
-rw-r--r--core/java/android/permission/IPermissionManager.aidl2
-rw-r--r--core/java/android/permission/PermissionCheckerManager.java186
-rw-r--r--core/java/android/permission/PermissionManager.java5
-rw-r--r--core/java/android/speech/RecognitionService.java125
12 files changed, 619 insertions, 224 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1415212ef07d..eb14c11e7e3c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -708,6 +708,62 @@ public class AppOpsManager {
}
}
+ /**
+ * Attribution chain flag: specifies that this is the accessor. When
+ * an app A accesses the data that is then passed to app B that is then
+ * passed to C, we call app A accessor, app B intermediary, and app C
+ * receiver. If A accesses the data for itself, then it is the accessor
+ * and the receiver.
+ * @hide
+ */
+ @TestApi
+ public static final int ATTRIBUTION_FLAG_ACCESSOR = 0x1;
+
+ /**
+ * Attribution chain flag: specifies that this is the intermediary. When
+ * an app A accesses the data that is then passed to app B that is then
+ * passed to C, we call app A accessor, app B intermediary, and app C
+ * receiver. If A accesses the data for itself, then it is the accessor
+ * and the receiver.
+ * @hide
+ */
+ @TestApi
+ public static final int ATTRIBUTION_FLAG_INTERMEDIARY = 0x2;
+
+ /**
+ * Attribution chain flag: specifies that this is the receiver. When
+ * an app A accesses the data that is then passed to app B that is then
+ * passed to C, we call app A accessor, app B intermediary, and app C
+ * receiver. If A accesses the data for itself, then it is the accessor
+ * and the receiver.
+ * @hide
+ */
+ @TestApi
+ public static final int ATTRIBUTION_FLAG_RECEIVER = 0x4;
+
+ /**
+ * No attribution flags.
+ * @hide
+ */
+ @TestApi
+ public static final int ATTRIBUTION_FLAGS_NONE = 0x0;
+
+ /**
+ * No attribution chain id.
+ * @hide
+ */
+ @TestApi
+ public static final int ATTRIBUTION_CHAIN_ID_NONE = -1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ ATTRIBUTION_FLAG_ACCESSOR,
+ ATTRIBUTION_FLAG_INTERMEDIARY,
+ ATTRIBUTION_FLAG_RECEIVER
+ })
+ public @interface AttributionFlags {}
+
// These constants are redefined here to work around a metalava limitation/bug where
// @IntDef is not able to see @hide symbols when they are hidden via package hiding:
// frameworks/base/core/java/com/android/internal/package.html
@@ -7063,6 +7119,25 @@ public class AppOpsManager {
*/
void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
boolean active);
+
+ /**
+ * Called when the active state of an app-op changes.
+ *
+ * @param op The operation that changed.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The operation's attribution tag.
+ * @param active Whether the operation became active or inactive.
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
+ * @hide
+ */
+ @TestApi
+ default void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean active, @AttributionFlags
+ int attributionFlags, int attributionChainId) {
+ onOpActiveChanged(op, uid, packageName, active);
+ }
}
/**
@@ -7684,14 +7759,17 @@ public class AppOpsManager {
}
cb = new IAppOpsActiveCallback.Stub() {
@Override
- public void opActiveChanged(int op, int uid, String packageName, boolean active) {
+ public void opActiveChanged(int op, int uid, String packageName,
+ String attributionTag, boolean active, @AttributionFlags
+ int attributionFlags, int attributionChainId) {
executor.execute(() -> {
if (callback instanceof OnOpActiveChangedInternalListener) {
((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
uid, packageName, active);
}
if (sOpToString[op] != null) {
- callback.onOpActiveChanged(sOpToString[op], uid, packageName, active);
+ callback.onOpActiveChanged(sOpToString[op], uid, packageName,
+ attributionTag, active, attributionFlags, attributionChainId);
}
});
}
@@ -8169,8 +8247,9 @@ public class AppOpsManager {
public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
@Nullable String proxiedAttributionTag, @Nullable String message) {
return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)),
- message, /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
+ mContext.getAttributionSource().getToken())), message,
+ /*skipProxyOperation*/ false);
}
/**
@@ -8255,8 +8334,8 @@ public class AppOpsManager {
int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource(
mContext.getAttributionSource(), new AttributionSource(proxiedUid,
- proxiedPackageName, proxiedAttributionTag)), message,
- /*skipProxyOperation*/ false);
+ proxiedPackageName, proxiedAttributionTag, mContext.getAttributionSource()
+ .getToken())), message,/*skipProxyOperation*/ false);
}
/**
@@ -8592,6 +8671,29 @@ public class AppOpsManager {
*/
public int startOpNoThrow(int op, int uid, @NonNull String packageName,
boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message) {
+ return startOpNoThrow(mContext.getAttributionSource().getToken(), op, uid, packageName,
+ startIfModeDefault, attributionTag, message);
+ }
+
+ /**
+ * @see #startOpNoThrow(String, int, String, String, String)
+ *
+ * @hide
+ */
+ public int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName,
+ boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message) {
+ return startOpNoThrow(token, op, uid, packageName, startIfModeDefault, attributionTag,
+ message, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE);
+ }
+
+ /**
+ * @see #startOpNoThrow(String, int, String, String, String)
+ *
+ * @hide
+ */
+ public int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName,
+ boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
try {
collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
@@ -8604,9 +8706,9 @@ public class AppOpsManager {
}
}
- SyncNotedAppOp syncOp = mService.startOperation(getClientId(), op, uid, packageName,
+ SyncNotedAppOp syncOp = mService.startOperation(token, op, uid, packageName,
attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message,
- shouldCollectMessage);
+ shouldCollectMessage, attributionFlags, attributionChainId);
if (syncOp.getOpMode() == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
@@ -8643,8 +8745,9 @@ public class AppOpsManager {
public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
@Nullable String proxiedAttributionTag, @Nullable String message) {
return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)),
- message, /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
+ mContext.getAttributionSource().getToken())), message,
+ /*skipProxyOperation*/ false);
}
/**
@@ -8690,8 +8793,9 @@ public class AppOpsManager {
@Nullable String message) {
return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource(
mContext.getAttributionSource(), new AttributionSource(proxiedUid,
- proxiedPackageName, proxiedAttributionTag)), message,
- /*skipProxyOperation*/ false);
+ proxiedPackageName, proxiedAttributionTag,
+ mContext.getAttributionSource().getToken())), message,
+ /*skipProxyOperation*/ false);
}
/**
@@ -8705,6 +8809,23 @@ public class AppOpsManager {
*/
public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean skipProxyOperation) {
+ return startProxyOpNoThrow(op, attributionSource, message, skipProxyOperation,
+ ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE);
+ }
+
+ /**
+ * Like {@link #startProxyOp(String, AttributionSource, String)} but instead
+ * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED} and
+ * the checks is for the attribution chain specified by the {@link AttributionSource}.
+ *
+ * @see #startProxyOp(String, AttributionSource, String)
+ *
+ * @hide
+ */
+ public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean skipProxyOperation, @AttributionFlags
+ int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
+ int attributionChainId) {
try {
collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(
@@ -8719,9 +8840,10 @@ public class AppOpsManager {
}
}
- SyncNotedAppOp syncOp = mService.startProxyOperation(getClientId(), op,
+ SyncNotedAppOp syncOp = mService.startProxyOperation(op,
attributionSource, false, collectionMode == COLLECT_ASYNC, message,
- shouldCollectMessage, skipProxyOperation);
+ shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId);
if (syncOp.getOpMode() == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
@@ -8785,8 +8907,18 @@ public class AppOpsManager {
*/
public void finishOp(int op, int uid, @NonNull String packageName,
@Nullable String attributionTag) {
+ finishOp(mContext.getAttributionSource().getToken(), op, uid, packageName, attributionTag);
+ }
+
+ /**
+ * @see #finishOp(String, int, String, String)
+ *
+ * @hide
+ */
+ public void finishOp(IBinder token, int op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag) {
try {
- mService.finishOperation(getClientId(), op, uid, packageName, attributionTag);
+ mService.finishOperation(token, op, uid, packageName, attributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8807,23 +8939,26 @@ public class AppOpsManager {
public void finishProxyOp(@NonNull String op, int proxiedUid,
@NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)));
+ new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
+ mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false);
}
/**
* Report that an application is no longer performing an operation that had previously
- * been started with {@link #startProxyOp(String, AttributionSource, String)}. There is no
- * validation of input or result; the parameters supplied here must be the exact same ones
- * previously passed in when starting the operation.
+ * been started with {@link #startProxyOp(String, AttributionSource, String, boolean)}. There
+ * is no validation of input or result; the parameters supplied here must be the exact same
+ * ones previously passed in when starting the operation.
*
* @param op The operation which was started
* @param attributionSource The permission identity for which to finish
+ * @param skipProxyOperation Whether to skip the proxy finish.
*
* @hide
*/
- public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource) {
+ public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource,
+ boolean skipProxyOperation) {
try {
- mService.finishProxyOperation(getClientId(), strOpToOp(op), attributionSource);
+ mService.finishProxyOperation(strOpToOp(op), attributionSource, skipProxyOperation);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index 2de0ddb17646..a757e32d0d75 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -16,6 +16,7 @@
package android.app;
+import android.app.AppOpsManager.AttributionFlags;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.AttributionSource;
@@ -24,13 +25,13 @@ import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.NonaFunction;
-import com.android.internal.util.function.OctFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuintFunction;
import com.android.internal.util.function.TriFunction;
+import com.android.internal.util.function.UndecFunction;
/**
* App ops service local interface.
@@ -52,8 +53,8 @@ public abstract class AppOpsManagerInternal {
* @return The app op check result.
*/
int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag,
- boolean raw,
- QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl);
+ boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer>
+ superImpl);
/**
* Allows overriding check audio operation behavior.
@@ -116,20 +117,22 @@ public abstract class AppOpsManagerInternal {
* @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected
* @param message The message in the async noted op
* @param shouldCollectMessage whether to collect messages
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
* @param superImpl The super implementation.
* @return The app op note result.
*/
SyncNotedAppOp startOperation(IBinder token, int code, int uid,
@Nullable String packageName, @Nullable String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage, @NonNull NonaFunction<
- IBinder, Integer, Integer, String, String, Boolean, Boolean, String,
- Boolean, SyncNotedAppOp> superImpl);
+ @Nullable String message, boolean shouldCollectMessage,
+ @AttributionFlags int attributionFlags, int attributionChainId,
+ @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean,
+ Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl);
/**
* Allows overriding start proxy operation behavior.
*
- * @param token The client state.
* @param code The op code to start.
* @param attributionSource The permission identity of the caller.
* @param startIfModeDefault Whether to start the op of the mode is default.
@@ -137,26 +140,29 @@ public abstract class AppOpsManagerInternal {
* @param message The message in the async noted op
* @param shouldCollectMessage whether to collect messages
* @param skipProxyOperation Whether to skip the proxy portion of the operation
+ * @param proxyAttributionFlags The attribution flags for the proxy.
+ * @param proxiedAttributionFlags The attribution flags for the proxied.
+ * @oaram attributionChainId The id of the attribution chain this operation is a part of.
* @param superImpl The super implementation.
* @return The app op note result.
*/
- SyncNotedAppOp startProxyOperation(IBinder token, int code,
- @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
- boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
- boolean skipProxyOperation, @NonNull OctFunction<IBinder, Integer,
- AttributionSource, Boolean, Boolean, String, Boolean, Boolean,
+ SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
+ boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
+ int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
+ int attributionChainId, @NonNull DecFunction<Integer, AttributionSource, Boolean,
+ Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
SyncNotedAppOp> superImpl);
/**
* Allows overriding finish proxy op.
*
- * @param clientId Client state token.
* @param code The op code to finish.
* @param attributionSource The permission identity of the caller.
*/
- void finishProxyOperation(IBinder clientId, int code,
- @NonNull AttributionSource attributionSource,
- @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl);
+ void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ boolean skipProxyOperation,
+ @NonNull TriFunction<Integer, AttributionSource, Boolean, Void> superImpl);
}
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index daef8b1eae08..16b6ea5bcf42 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3157,13 +3157,7 @@ class ContextImpl extends Context {
// If we want to access protected data on behalf of another app we need to
// tell the OS that we opt in to participate in the attribution chain.
if (nextAttributionSource != null) {
- // If an app happened to stub the internal OS for testing the registration method
- // can return null. In this case we keep the current untrusted attribution source.
- final AttributionSource registeredAttributionSource = getSystemService(
- PermissionManager.class).registerAttributionSource(attributionSource);
- if (registeredAttributionSource != null) {
- return registeredAttributionSource;
- }
+ getSystemService(PermissionManager.class).registerAttributionSource(attributionSource);
}
return attributionSource;
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 91dad2a1f13c..871d48b07a20 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -186,6 +186,7 @@ import android.os.incremental.IIncrementalService;
import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.permission.LegacyPermissionManager;
+import android.permission.PermissionCheckerManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
import android.print.IPrintManager;
@@ -1334,6 +1335,14 @@ public final class SystemServiceRegistry {
ctx.getMainThreadHandler());
}});
+ registerService(Context.PERMISSION_CHECKER_SERVICE, PermissionCheckerManager.class,
+ new CachedServiceFetcher<PermissionCheckerManager>() {
+ @Override
+ public PermissionCheckerManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new PermissionCheckerManager(ctx.getOuterContext());
+ }});
+
registerService(Context.DYNAMIC_SYSTEM_SERVICE, DynamicSystemManager.class,
new CachedServiceFetcher<DynamicSystemManager>() {
@Override
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 7ab731f15ad2..1dda6374a474 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
@@ -94,14 +95,22 @@ public final class AttributionSource implements Parcelable {
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag) {
- this(uid, packageName, attributionTag, /*next*/ null);
+ this(uid, packageName, attributionTag, new Binder());
}
/** @hide */
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
- @Nullable String attributionTag, @Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, /*renouncedPermissions*/ null, next);
+ @Nullable String attributionTag, @NonNull IBinder token) {
+ this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
+ /*next*/ null);
+ }
+
+ /** @hide */
+ public AttributionSource(int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token,
+ @Nullable AttributionSource next) {
+ this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next);
}
/** @hide */
@@ -109,28 +118,32 @@ public final class AttributionSource implements Parcelable {
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
@Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, /*token*/ null, (renouncedPermissions != null)
+ this(uid, packageName, attributionTag, (renouncedPermissions != null)
? renouncedPermissions.toArray(new String[0]) : null, next);
}
/** @hide */
- public AttributionSource(@NonNull AttributionSource current,
- @Nullable AttributionSource next) {
+ public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
- /*token*/ null, /*renouncedPermissions*/ null, next);
+ current.getToken(), current.mAttributionSourceState.renouncedPermissions, next);
}
AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
- @Nullable IBinder token, @Nullable String[] renouncedPermissions,
+ @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
+ this(uid, packageName, attributionTag, new Binder(), renouncedPermissions, next);
+ }
+
+ AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
+ @NonNull IBinder token, @Nullable String[] renouncedPermissions,
@Nullable AttributionSource next) {
mAttributionSourceState = new AttributionSourceState();
mAttributionSourceState.uid = uid;
+ mAttributionSourceState.token = token;
mAttributionSourceState.packageName = packageName;
mAttributionSourceState.attributionTag = attributionTag;
- mAttributionSourceState.token = token;
mAttributionSourceState.renouncedPermissions = renouncedPermissions;
mAttributionSourceState.next = (next != null) ? new AttributionSourceState[]
- {next.mAttributionSourceState} : null;
+ {next.mAttributionSourceState} : new AttributionSourceState[0];
}
AttributionSource(@NonNull Parcel in) {
@@ -145,18 +158,12 @@ public final class AttributionSource implements Parcelable {
/** @hide */
public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
- getToken(), mAttributionSourceState.renouncedPermissions, next);
- }
-
- /** @hide */
- public AttributionSource withToken(@Nullable IBinder token) {
- return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
- token, mAttributionSourceState.renouncedPermissions, getNext());
+ mAttributionSourceState.renouncedPermissions, next);
}
/** @hide */
public AttributionSource withPackageName(@Nullable String packageName) {
- return new AttributionSource(getUid(), packageName, getAttributionTag(), getToken(),
+ return new AttributionSource(getUid(), packageName, getAttributionTag(),
mAttributionSourceState.renouncedPermissions, getNext());
}
@@ -165,6 +172,45 @@ public final class AttributionSource implements Parcelable {
return mAttributionSourceState;
}
+ /** @hide */
+ public @NonNull ScopedParcelState asScopedParcelState() {
+ return new ScopedParcelState(this);
+ }
+
+ /** @hide */
+ public static AttributionSource myAttributionSource() {
+ return new AttributionSource(Process.myUid(), ActivityThread.currentOpPackageName(),
+ /*attributionTag*/ null, (String[]) /*renouncedPermissions*/ null, /*next*/ null);
+ }
+
+ /**
+ * This is a scoped object that exposes the content of an attribution source
+ * as a parcel. This is useful when passing one to native and avoid custom
+ * conversion logic from Java to native state that needs to be kept in sync
+ * as attribution source evolves. This way we use the same logic for passing
+ * to native as the ones for passing in an IPC - in both cases this is the
+ * same auto generated code.
+ *
+ * @hide
+ */
+ public static class ScopedParcelState implements AutoCloseable {
+ private final Parcel mParcel;
+
+ public @NonNull Parcel getParcel() {
+ return mParcel;
+ }
+
+ public ScopedParcelState(AttributionSource attributionSource) {
+ mParcel = Parcel.obtain();
+ attributionSource.writeToParcel(mParcel, 0);
+ mParcel.setDataPosition(0);
+ }
+
+ public void close() {
+ mParcel.recycle();
+ }
+ }
+
/**
* If you are handling an IPC and you don't trust the caller you need to validate
* whether the attribution source is one for the calling app to prevent the caller
@@ -209,7 +255,8 @@ public final class AttributionSource implements Parcelable {
"attributionTag = " + mAttributionSourceState.attributionTag + ", " +
"token = " + mAttributionSourceState.token + ", " +
"next = " + (mAttributionSourceState.next != null
- ? mAttributionSourceState.next[0]: null) +
+ && mAttributionSourceState.next.length > 0
+ ? mAttributionSourceState.next[0] : null) +
" }";
}
return super.toString();
@@ -221,7 +268,8 @@ public final class AttributionSource implements Parcelable {
* @hide
*/
public int getNextUid() {
- if (mAttributionSourceState.next != null) {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
return mAttributionSourceState.next[0].uid;
}
return Process.INVALID_UID;
@@ -233,26 +281,42 @@ public final class AttributionSource implements Parcelable {
* @hide
*/
public @Nullable String getNextPackageName() {
- if (mAttributionSourceState.next != null) {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
return mAttributionSourceState.next[0].packageName;
}
return null;
}
/**
- * @return The nexxt package's attribution tag that would receive
+ * @return The next package's attribution tag that would receive
* the permission protected data.
*
* @hide
*/
public @Nullable String getNextAttributionTag() {
- if (mAttributionSourceState.next != null) {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
return mAttributionSourceState.next[0].attributionTag;
}
return null;
}
/**
+ * @return The next package's token that would receive
+ * the permission protected data.
+ *
+ * @hide
+ */
+ public @Nullable IBinder getNextToken() {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
+ return mAttributionSourceState.next[0].token;
+ }
+ return null;
+ }
+
+ /**
* Checks whether this attribution source can be trusted. That is whether
* the app it refers to created it and provided to the attribution chain.
*
@@ -311,7 +375,7 @@ public final class AttributionSource implements Parcelable {
*
* @hide
*/
- public @Nullable IBinder getToken() {
+ public @NonNull IBinder getToken() {
return mAttributionSourceState.token;
}
@@ -319,7 +383,8 @@ public final class AttributionSource implements Parcelable {
* The next app to receive the permission protected data.
*/
public @Nullable AttributionSource getNext() {
- if (mNextCached == null && mAttributionSourceState.next != null) {
+ if (mNextCached == null && mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
mNextCached = new AttributionSource(mAttributionSourceState.next[0]);
}
return mNextCached;
@@ -442,7 +507,7 @@ public final class AttributionSource implements Parcelable {
@RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x10;
+ mBuilderFieldsSet |= 0x8;
mAttributionSourceState.renouncedPermissions = (value != null)
? value.toArray(new String[0]) : null;
return this;
@@ -453,9 +518,9 @@ public final class AttributionSource implements Parcelable {
*/
public @NonNull Builder setNext(@Nullable AttributionSource value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20;
+ mBuilderFieldsSet |= 0x10;
mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
- {value.mAttributionSourceState} : null;
+ {value.mAttributionSourceState} : mAttributionSourceState.next;
return this;
}
@@ -471,14 +536,16 @@ public final class AttributionSource implements Parcelable {
mAttributionSourceState.attributionTag = null;
}
if ((mBuilderFieldsSet & 0x8) == 0) {
- mAttributionSourceState.token = null;
- }
- if ((mBuilderFieldsSet & 0x10) == 0) {
mAttributionSourceState.renouncedPermissions = null;
}
- if ((mBuilderFieldsSet & 0x20) == 0) {
+ if ((mBuilderFieldsSet & 0x10) == 0) {
mAttributionSourceState.next = null;
}
+ mAttributionSourceState.token = new Binder();
+ if (mAttributionSourceState.next == null) {
+ // The NDK aidl backend doesn't support null parcelable arrays.
+ mAttributionSourceState.next = new AttributionSourceState[0];
+ }
return new AttributionSource(mAttributionSourceState);
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 88686a309ee4..dc29c5e25f3c 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -49,6 +49,7 @@ import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.StorageManager;
+import android.permission.PermissionCheckerManager;
import android.text.TextUtils;
import android.util.Log;
@@ -670,7 +671,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
}
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
private void enforceFilePermission(@NonNull AttributionSource attributionSource,
Uri uri, String mode)
throws FileNotFoundException, SecurityException {
@@ -687,7 +688,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
}
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
private int enforceReadPermission(@NonNull AttributionSource attributionSource, Uri uri)
throws SecurityException {
final int result = enforceReadPermissionInner(uri, attributionSource);
@@ -705,7 +706,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
return PermissionChecker.PERMISSION_GRANTED;
}
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
private int enforceWritePermission(@NonNull AttributionSource attributionSource, Uri uri)
throws SecurityException {
final int result = enforceWritePermissionInner(uri, attributionSource);
@@ -738,7 +739,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
* Verify that calling app holds both the given permission and any app-op
* associated with that permission.
*/
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
private int checkPermission(String permission,
@NonNull AttributionSource attributionSource) {
if (Binder.getCallingPid() == Process.myPid()) {
@@ -753,7 +754,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
/** {@hide} */
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
protected int enforceReadPermissionInner(Uri uri,
@NonNull AttributionSource attributionSource) throws SecurityException {
final Context context = getContext();
@@ -836,7 +837,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
/** {@hide} */
- @PermissionChecker.PermissionResult
+ @PermissionCheckerManager.PermissionResult
protected int enforceWritePermissionInner(Uri uri,
@NonNull AttributionSource attributionSource) throws SecurityException {
final Context context = getContext();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 886cd7f48d35..ea0e321377c8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4836,6 +4836,14 @@ public abstract class Context {
public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller";
/**
+ * Official published name of the (internal) permission checker service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String PERMISSION_CHECKER_SERVICE = "permission_checker";
+
+ /**
* Use with {@link #getSystemService(String) to retrieve an
* {@link android.apphibernation.AppHibernationManager}} for
* communicating with the hibernation service.
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 66e088359459..a167cb3d851f 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,19 +16,14 @@
package android.content;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.os.Binder;
-import android.os.IBinder;
import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.permission.IPermissionChecker;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionCheckerManager.PermissionResult;
/**
* This class provides permission check APIs that verify both the
@@ -75,7 +70,7 @@ public final class PermissionChecker {
*
* @hide
*/
- public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
+ public static final int PERMISSION_GRANTED = PermissionCheckerManager.PERMISSION_GRANTED;
/**
* The permission is denied. Applicable only to runtime and app op permissions.
@@ -89,7 +84,8 @@ public final class PermissionChecker {
*
* @hide
*/
- public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
+ public static final int PERMISSION_SOFT_DENIED =
+ PermissionCheckerManager.PERMISSION_SOFT_DENIED;
/**
* The permission is denied.
@@ -103,18 +99,12 @@ public final class PermissionChecker {
*
* @hide
*/
- public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED;
+ public static final int PERMISSION_HARD_DENIED =
+ PermissionCheckerManager.PERMISSION_HARD_DENIED;
/** Constant when the PID for which we check permissions is unknown. */
public static final int PID_UNKNOWN = -1;
- /** @hide */
- @IntDef({PERMISSION_GRANTED,
- PERMISSION_SOFT_DENIED,
- PERMISSION_HARD_DENIED})
- @Retention(RetentionPolicy.SOURCE)
- public @interface PermissionResult {}
-
private static volatile IPermissionChecker sService;
private PermissionChecker() {
@@ -157,7 +147,7 @@ public final class PermissionChecker {
*
* @see #checkPermissionForPreflight(Context, String, int, int, String)
*/
- @PermissionResult
+ @PermissionCheckerManager.PermissionResult
public static int checkPermissionForDataDelivery(@NonNull Context context,
@NonNull String permission, int pid, int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) {
@@ -321,19 +311,13 @@ public final class PermissionChecker {
message, startDataDelivery, /*fromDatasource*/ false);
}
+ @SuppressWarnings("ConstantConditions")
private static int checkPermissionForDataDeliveryCommon(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean startDataDelivery, boolean fromDatasource) {
- // If the check failed in the middle of the chain, finish any started op.
- try {
- final int result = getPermissionCheckerService().checkPermission(permission,
- attributionSource.asState(), message, true /*forDataDelivery*/,
- startDataDelivery, fromDatasource);
- return result;
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class).checkPermission(permission,
+ attributionSource.asState(), message, true /*forDataDelivery*/, startDataDelivery,
+ fromDatasource, AppOpsManager.OP_NONE);
}
/**
@@ -365,17 +349,13 @@ public final class PermissionChecker {
* @see #checkPermissionForPreflight(Context, String, AttributionSource)
*/
@PermissionResult
+ @SuppressWarnings("ConstantConditions")
public static int checkPermissionAndStartDataDelivery(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkPermission(permission,
- attributionSource.asState(), message, true /*forDataDelivery*/,
- /*startDataDelivery*/ true, /*fromDatasource*/ false);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class).checkPermission(
+ permission, attributionSource.asState(), message, true /*forDataDelivery*/,
+ /*startDataDelivery*/ true, /*fromDatasource*/ false, AppOpsManager.OP_NONE);
}
/**
@@ -404,17 +384,13 @@ public final class PermissionChecker {
* @see #finishDataDelivery(Context, String, AttributionSource)
*/
@PermissionResult
+ @SuppressWarnings("ConstantConditions")
public static int startOpForDataDelivery(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkOp(
- AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
- true /*forDataDelivery*/, true /*startDataDelivery*/);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class).checkOp(
+ AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+ true /*forDataDelivery*/, true /*startDataDelivery*/);
}
/**
@@ -428,13 +404,32 @@ public final class PermissionChecker {
* @see #startOpForDataDelivery(Context, String, AttributionSource, String)
* @see #checkPermissionAndStartDataDelivery(Context, String, AttributionSource, String)
*/
+ @SuppressWarnings("ConstantConditions")
public static void finishDataDelivery(@NonNull Context context, @NonNull String op,
@NonNull AttributionSource attributionSource) {
- try {
- getPermissionCheckerService().finishDataDelivery(op, attributionSource.asState());
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
+ context.getSystemService(PermissionCheckerManager.class).finishDataDelivery(
+ AppOpsManager.strOpToOp(op), attributionSource.asState(),
+ /*fromDatasource*/ false);
+ }
+
+ /**
+ * Finishes an ongoing op for data access chain described by the given {@link
+ * AttributionSource}. Call this method if you are the datasource which would
+ * not finish an op for your attribution source as it was not started.
+ *
+ * @param context Context for accessing resources.
+ * @param op The op to finish.
+ * @param attributionSource The identity for which finish op.
+ *
+ * @see #startOpForDataDelivery(Context, String, AttributionSource, String)
+ * @see #checkPermissionAndStartDataDelivery(Context, String, AttributionSource, String)
+ */
+ @SuppressWarnings("ConstantConditions")
+ public static void finishDataDeliveryFromDatasource(@NonNull Context context,
+ @NonNull String op, @NonNull AttributionSource attributionSource) {
+ context.getSystemService(PermissionCheckerManager.class).finishDataDelivery(
+ AppOpsManager.strOpToOp(op), attributionSource.asState(),
+ /*fromDatasource*/ true);
}
/**
@@ -466,17 +461,13 @@ public final class PermissionChecker {
* @see #checkOpForDataDelivery(Context, String, AttributionSource, String)
*/
@PermissionResult
+ @SuppressWarnings("ConstantConditions")
public static int checkOpForPreflight(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
- attributionSource.asState(), message, false /*forDataDelivery*/,
- false /*startDataDelivery*/);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class).checkOp(
+ AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+ false /*forDataDelivery*/, false /*startDataDelivery*/);
}
/**
@@ -505,17 +496,13 @@ public final class PermissionChecker {
* @see #checkOpForPreflight(Context, String, AttributionSource, String)
*/
@PermissionResult
+ @SuppressWarnings("ConstantConditions")
public static int checkOpForDataDelivery(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- try {
- return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
- attributionSource.asState(), message, true /*forDataDelivery*/,
- false /*startDataDelivery*/);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class).checkOp(
+ AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+ true /*forDataDelivery*/, false /*startDataDelivery*/);
}
/**
@@ -584,16 +571,13 @@ public final class PermissionChecker {
* String, boolean)
*/
@PermissionResult
+ @SuppressWarnings("ConstantConditions")
public static int checkPermissionForPreflight(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource) {
- try {
- return getPermissionCheckerService().checkPermission(permission,
- attributionSource.asState(), null /*message*/, false /*forDataDelivery*/,
- /*startDataDelivery*/ false, /*fromDatasource*/ false);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return PERMISSION_HARD_DENIED;
+ return context.getSystemService(PermissionCheckerManager.class)
+ .checkPermission(permission, attributionSource.asState(), null /*message*/,
+ false /*forDataDelivery*/, /*startDataDelivery*/ false, /*fromDatasource*/ false,
+ AppOpsManager.OP_NONE);
}
/**
@@ -827,13 +811,4 @@ public final class PermissionChecker {
return checkPermissionForPreflight(context, permission, Binder.getCallingPid(),
Binder.getCallingUid(), packageName);
}
-
- private static @NonNull IPermissionChecker getPermissionCheckerService() {
- // Race is fine, we may end up looking up the same instance twice, no big deal.
- if (sService == null) {
- final IBinder service = ServiceManager.getService("permission_checker");
- sService = IPermissionChecker.Stub.asInterface(service);
- }
- return sService;
- }
}
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index ef075e1efbff..9ab69552f1a0 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -87,7 +87,7 @@ interface IPermissionManager {
boolean isAutoRevokeExempted(String packageName, int userId);
- AttributionSource registerAttributionSource(in AttributionSource source);
+ void registerAttributionSource(in AttributionSource source);
boolean isRegisteredAttributionSource(in AttributionSource source);
}
diff --git a/core/java/android/permission/PermissionCheckerManager.java b/core/java/android/permission/PermissionCheckerManager.java
new file mode 100644
index 000000000000..7523816250b0
--- /dev/null
+++ b/core/java/android/permission/PermissionCheckerManager.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2018 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.permission;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.AttributionSourceState;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Manager for checking runtime and app op permissions. This is a temporary
+ * class and we may fold its function in the PermissionManager once the
+ * permission re-architecture starts falling into place. The main benefit
+ * of this class is to allow context level caching.
+ *
+ * @hide
+ */
+public class PermissionCheckerManager {
+
+ /**
+ * The permission is granted.
+ */
+ public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
+
+ /**
+ * The permission is denied. Applicable only to runtime and app op permissions.
+ *
+ * <p>Returned when:
+ * <ul>
+ * <li>the runtime permission is granted, but the corresponding app op is denied
+ * for runtime permissions.</li>
+ * <li>the app ops is ignored for app op permissions.</li>
+ * </ul>
+ */
+ public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
+
+ /**
+ * The permission is denied.
+ *
+ * <p>Returned when:
+ * <ul>
+ * <li>the permission is denied for non app op permissions.</li>
+ * <li>the app op is denied or app op is {@link AppOpsManager#MODE_DEFAULT}
+ * and permission is denied.</li>
+ * </ul>
+ */
+ public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED;
+
+ /** @hide */
+ @IntDef({PERMISSION_GRANTED,
+ PERMISSION_SOFT_DENIED,
+ PERMISSION_HARD_DENIED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionResult {}
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final IPermissionChecker mService;
+
+ @NonNull
+ private final PackageManager mPackageManager;
+
+ public PermissionCheckerManager(@NonNull Context context)
+ throws ServiceManager.ServiceNotFoundException {
+ mContext = context;
+ mService = IPermissionChecker.Stub.asInterface(ServiceManager.getServiceOrThrow(
+ Context.PERMISSION_CHECKER_SERVICE));
+ mPackageManager = context.getPackageManager();
+ }
+
+ /**
+ * Checks a permission by validating the entire attribution source chain. If the
+ * permission is associated with an app op the op is also noted/started for the
+ * entire attribution chain.
+ *
+ * @param permission The permission
+ * @param attributionSource The attribution chain to check.
+ * @param message Message associated with the permission if permission has an app op
+ * @param forDataDelivery Whether the check is for delivering data if permission has an app op
+ * @param startDataDelivery Whether to start data delivery (start op) if permission has
+ * an app op
+ * @param fromDatasource Whether the check is by a datasource (skip checks for the
+ * first attribution source in the chain as this is the datasource)
+ * @param attributedOp Alternative app op to attribute
+ * @return The permission check result.
+ */
+ @PermissionResult
+ public int checkPermission(@NonNull String permission,
+ @NonNull AttributionSourceState attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
+ int attributedOp) {
+ Objects.requireNonNull(permission);
+ Objects.requireNonNull(attributionSource);
+ // Fast path for non-runtime, non-op permissions where the attribution chain has
+ // length one. This is the majority of the cases and we want these to be fast by
+ // hitting the local in process permission cache.
+ if (AppOpsManager.permissionToOpCode(permission) == AppOpsManager.OP_NONE) {
+ if (fromDatasource) {
+ if (attributionSource.next != null && attributionSource.next.length > 0) {
+ return mContext.checkPermission(permission, attributionSource.next[0].pid,
+ attributionSource.next[0].uid) == PackageManager.PERMISSION_GRANTED
+ ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+ }
+ } else {
+ return (mContext.checkPermission(permission, attributionSource.pid,
+ attributionSource.uid) == PackageManager.PERMISSION_GRANTED)
+ ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+ }
+ }
+ try {
+ return mService.checkPermission(permission, attributionSource, message, forDataDelivery,
+ startDataDelivery, fromDatasource, attributedOp);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return PERMISSION_HARD_DENIED;
+ }
+
+ /**
+ * Finishes an app op by validating the entire attribution source chain.
+ *
+ * @param op The op to finish.
+ * @param attributionSource The attribution chain to finish.
+ * @param fromDatasource Whether the finish is by a datasource (skip finish for the
+ * first attribution source in the chain as this is the datasource)
+ */
+ public void finishDataDelivery(int op, @NonNull AttributionSourceState attributionSource,
+ boolean fromDatasource) {
+ Objects.requireNonNull(attributionSource);
+ try {
+ mService.finishDataDelivery(op, attributionSource, fromDatasource);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks an app op by validating the entire attribution source chain. The op is
+ * also noted/started for the entire attribution chain.
+ *
+ * @param op The op to check.
+ * @param attributionSource The attribution chain to check.
+ * @param message Message associated with the permission if permission has an app op
+ * @param forDataDelivery Whether the check is for delivering data if permission has an app op
+ * @param startDataDelivery Whether to start data delivery (start op) if permission has
+ * an app op
+ * @return The op check result.
+ */
+ @PermissionResult
+ public int checkOp(int op, @NonNull AttributionSourceState attributionSource,
+ @Nullable String message, boolean forDataDelivery, boolean startDataDelivery) {
+ Objects.requireNonNull(attributionSource);
+ try {
+ return mService.checkOp(op, attributionSource, message, forDataDelivery,
+ startDataDelivery);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return PERMISSION_HARD_DENIED;
+ }
+}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index d490e7a7b454..f3cc35b32223 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1156,13 +1156,12 @@ public final class PermissionManager {
* @hide
*/
@TestApi
- public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) {
+ public void registerAttributionSource(@NonNull AttributionSource source) {
try {
- return mPermissionManager.registerAttributionSource(source);
+ mPermissionManager.registerAttributionSource(source);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
- return null;
}
/**
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index bb48757988e9..b9ff5e7be86a 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -65,22 +65,20 @@ public abstract class RecognitionService extends Service {
private static final String TAG = "RecognitionService";
/** Debugging flag */
- private static final boolean DBG = true;
-
- private static final String RECORD_AUDIO_APP_OP =
- AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
- private static final int RECORD_AUDIO_APP_OP_CODE =
- AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO);
+ private static final boolean DBG = false;
/** Binder of the recognition service */
private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
/**
* The current callback of an application that invoked the
+ *
* {@link RecognitionService#onStartListening(Intent, Callback)} method
*/
private Callback mCurrentCallback = null;
+ private boolean mStartedDataDelivery;
+
private static final int MSG_START_LISTENING = 1;
private static final int MSG_STOP_LISTENING = 2;
@@ -120,6 +118,11 @@ public abstract class RecognitionService extends Service {
mCurrentCallback = new Callback(listener, attributionSource);
RecognitionService.this.onStartListening(intent, mCurrentCallback);
+ if (!checkPermissionAndStartDataDelivery()) {
+ listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
+ Log.i(TAG, "caller doesn't have permission:"
+ + Manifest.permission.RECORD_AUDIO);
+ }
} else {
listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
Log.i(TAG, "concurrent startListening received - ignoring this call");
@@ -152,13 +155,15 @@ public abstract class RecognitionService extends Service {
Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
} else { // the correct state
RecognitionService.this.onCancel(mCurrentCallback);
- mCurrentCallback = null;
+ dispatchClearCallback();
if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
}
}
private void dispatchClearCallback() {
+ finishDataDelivery();
mCurrentCallback = null;
+ mStartedDataDelivery = false;
}
private class StartListeningArgs {
@@ -177,7 +182,30 @@ public abstract class RecognitionService extends Service {
/**
* Notifies the service that it should start listening for speech.
- *
+ *
+ * <p> If you are recognizing speech from the microphone, in this callback you
+ * should create an attribution context for the caller such that when you access
+ * the mic the caller would be properly blamed (and their permission checked in
+ * the process) for accessing the microphone and that you served as a proxy for
+ * this sensitive data (and your permissions would be checked in the process).
+ * You should also open the mic in this callback via the attribution context
+ * and close the mic before returning the recognized result. If you don't do
+ * that then the caller would be blamed and you as being a proxy as well as you
+ * would get one more blame on yourself when you open the microphone.
+ *
+ * <pre>
+ * Context attributionContext = context.createContext(new ContextParams.Builder()
+ * .setNextAttributionSource(callback.getCallingAttributionSource())
+ * .build());
+ *
+ * AudioRecord recorder = AudioRecord.Builder()
+ * .setContext(attributionContext);
+ * . . .
+ * .build();
+ *
+ * recorder.startRecording()
+ * </pre>
+ *
* @param recognizerIntent contains parameters for the recognition to be performed. The intent
* may also contain optional extras, see {@link RecognizerIntent}. If these values are
* not set explicitly, default values should be used by the recognizer.
@@ -335,57 +363,13 @@ public abstract class RecognitionService extends Service {
return mCallingAttributionSource;
}
- boolean maybeStartAttribution() {
- if (DBG) {
- Log.i(TAG, "Starting attribution");
- }
-
- if (DBG && isProxyingRecordAudioToCaller()) {
- Log.i(TAG, "Proxying already in progress, not starting the attribution");
- }
-
- if (!isProxyingRecordAudioToCaller()) {
+ @NonNull Context getAttributionContextForCaller() {
+ if (mAttributionContext == null) {
mAttributionContext = createContext(new ContextParams.Builder()
.setNextAttributionSource(mCallingAttributionSource)
.build());
-
- final int result = PermissionChecker.checkPermissionAndStartDataDelivery(
- RecognitionService.this,
- Manifest.permission.RECORD_AUDIO,
- mAttributionContext.getAttributionSource(),
- /*message*/ null);
-
- return result == PermissionChecker.PERMISSION_GRANTED;
- }
- return false;
- }
-
- void maybeFinishAttribution() {
- if (DBG) {
- Log.i(TAG, "Finishing attribution");
- }
-
- if (DBG && !isProxyingRecordAudioToCaller()) {
- Log.i(TAG, "Not proxying currently, not finishing the attribution");
- }
-
- if (isProxyingRecordAudioToCaller()) {
- PermissionChecker.finishDataDelivery(
- RecognitionService.this,
- RECORD_AUDIO_APP_OP,
- mAttributionContext.getAttributionSource());
-
- mAttributionContext = null;
}
- }
-
- private boolean isProxyingRecordAudioToCaller() {
- final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
- return appOpsManager.isProxying(
- RECORD_AUDIO_APP_OP_CODE,
- getAttributionTag(),
- mCallingAttributionSource.getUid(),
- mCallingAttributionSource.getPackageName());
+ return mAttributionContext;
}
}
@@ -435,4 +419,35 @@ public abstract class RecognitionService extends Service {
mServiceRef.clear();
}
}
+
+ private boolean checkPermissionAndStartDataDelivery() {
+ if (isPerformingDataDelivery()) {
+ return true;
+ }
+ if (PermissionChecker.checkPermissionAndStartDataDelivery(
+ RecognitionService.this, Manifest.permission.RECORD_AUDIO,
+ mCurrentCallback.getAttributionContextForCaller().getAttributionSource(),
+ /*message*/ null) == PermissionChecker.PERMISSION_GRANTED) {
+ mStartedDataDelivery = true;
+ }
+ return mStartedDataDelivery;
+ }
+
+ void finishDataDelivery() {
+ if (mStartedDataDelivery) {
+ mStartedDataDelivery = false;
+ final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
+ PermissionChecker.finishDataDelivery(RecognitionService.this, op,
+ mCurrentCallback.getAttributionContextForCaller().getAttributionSource());
+ }
+ }
+
+ @SuppressWarnings("ConstantCondition")
+ private boolean isPerformingDataDelivery() {
+ final int op = AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO);
+ final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
+ return appOpsManager.isProxying(op, getAttributionTag(),
+ mCurrentCallback.getCallingAttributionSource().getUid(),
+ mCurrentCallback.getCallingAttributionSource().getPackageName());
+ }
}