Merge "Activity recognition source app op tracking" into sc-dev

This commit is contained in:
Svetoslav Ganov
2021-04-08 18:34:58 +00:00
committed by Android (Google) Code Review
8 changed files with 236 additions and 57 deletions

View File

@@ -69,6 +69,7 @@ package android {
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
field @Deprecated public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
field public static final String BYPASS_ROLE_QUALIFICATION = "android.permission.BYPASS_ROLE_QUALIFICATION";
field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";

View File

@@ -233,6 +233,8 @@ package android.app {
field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time";
field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time";
field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
field public static final String OPSTR_ACTIVITY_RECOGNITION = "android:activity_recognition";
field public static final String OPSTR_ACTIVITY_RECOGNITION_SOURCE = "android:activity_recognition_source";
field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
field public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
field public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone";

View File

@@ -1225,9 +1225,19 @@ public class AppOpsManager {
/** @hide */
public static final int OP_UWB_RANGING = AppProtoEnums.APP_OP_UWB_RANGING;
/**
* Activity recognition being accessed by an activity recognition source, which
* is a component that already has access since it is the one that detects
* activity recognition.
*
* @hide
*/
public static final int OP_ACTIVITY_RECOGNITION_SOURCE =
AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int _NUM_OP = 113;
public static final int _NUM_OP = 114;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1478,6 +1488,7 @@ public class AppOpsManager {
public static final String OPSTR_USE_BIOMETRIC = "android:use_biometric";
/** @hide Recognize physical activity. */
@TestApi
public static final String OPSTR_ACTIVITY_RECOGNITION = "android:activity_recognition";
/** @hide Financial app read sms. */
@@ -1643,6 +1654,17 @@ public class AppOpsManager {
/** @hide */
public static final String OPSTR_UWB_RANGING = "android:uwb_ranging";
/**
* Activity recognition being accessed by an activity recognition source, which
* is a component that already has access since it is the one that detects
* activity recognition.
*
* @hide
*/
@TestApi
public static final String OPSTR_ACTIVITY_RECOGNITION_SOURCE =
"android:activity_recognition_source";
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1853,6 +1875,7 @@ public class AppOpsManager {
OP_MANAGE_MEDIA, // MANAGE_MEDIA
OP_BLUETOOTH_CONNECT, // OP_BLUETOOTH_CONNECT
OP_UWB_RANGING, // OP_UWB_RANGING
OP_ACTIVITY_RECOGNITION_SOURCE // OP_ACTIVITY_RECOGNITION_SOURCE
};
/**
@@ -1972,6 +1995,7 @@ public class AppOpsManager {
OPSTR_MANAGE_MEDIA,
OPSTR_BLUETOOTH_CONNECT,
OPSTR_UWB_RANGING,
OPSTR_ACTIVITY_RECOGNITION_SOURCE
};
/**
@@ -2091,7 +2115,8 @@ public class AppOpsManager {
"COARSE_LOCATION_SOURCE",
"MANAGE_MEDIA",
"BLUETOOTH_CONNECT",
"UWB_RANGING"
"UWB_RANGING",
"ACTIVITY_RECOGNITION_SOURCE"
};
/**
@@ -2213,6 +2238,7 @@ public class AppOpsManager {
Manifest.permission.MANAGE_MEDIA,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.UWB_RANGING,
null, // no permission for OP_ACTIVITY_RECOGNITION_SOURCE,
};
/**
@@ -2334,6 +2360,7 @@ public class AppOpsManager {
null, // MANAGE_MEDIA
null, // BLUETOOTH_CONNECT
null, // UWB_RANGING
null, // ACTIVITY_RECOGNITION_SOURCE
};
/**
@@ -2454,6 +2481,7 @@ public class AppOpsManager {
null, // MANAGE_MEDIA
null, // BLUETOOTH_CONNECT
null, // UWB_RANGING
null // ACTIVITY_RECOGNITION_SOURCE
};
/**
@@ -2573,6 +2601,7 @@ public class AppOpsManager {
AppOpsManager.MODE_DEFAULT, // MANAGE_MEDIA
AppOpsManager.MODE_ALLOWED, // BLUETOOTH_CONNECT
AppOpsManager.MODE_ALLOWED, // UWB_RANGING
AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION_SOURCE
};
/**
@@ -2696,6 +2725,7 @@ public class AppOpsManager {
false, // MANAGE_MEDIA
false, // BLUETOOTH_CONNECT
false, // UWB_RANGING
false, // ACTIVITY_RECOGNITION_SOURCE
};
/**

View File

@@ -4240,6 +4240,15 @@
<permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
android:protectionLevel="signature|installer" />
<!-- @SystemApi Allows an application to bypass role qualification. This allows switching role
holders to otherwise non eligible holders. Only the shell is allowed to do this, the
qualification for the shell role itself cannot be bypassed, and each role needs to
explicitly allow bypassing qualification in its definition. The bypass state will not be
persisted across reboot.
@hide -->
<permission android:name="android.permission.BYPASS_ROLE_QUALIFICATION"
android:protectionLevel="internal|role" />
<!-- @SystemApi Allows an application to observe role holder changes.
@hide -->
<permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"

View File

@@ -1932,11 +1932,13 @@
<!-- The name of the package that will hold the speech recognizer role by default. -->
<string name="config_systemSpeechRecognizer" translatable="false"></string>
<!-- The name of the package that will hold the system Wi-Fi coex manager role. -->
<string name="config_systemWifiCoexManager" translateable="false"></string>
<string name="config_systemWifiCoexManager" translatable="false"></string>
<!-- The name of the package that will hold the wellbeing role. -->
<string name="config_systemWellbeing" translatable="false"></string>
<!-- The name of the package that will hold the television notification handler role -->
<string name="config_systemTelevisionNotificationHandler" translatable="false"></string>
<!-- The name of the package that will hold the system activity recognizer role. -->
<string name="config_systemActivityRecognizer" translatable="false"></string>
<!-- The name of the package that will be allowed to change its components' label/icon. -->
<string name="config_overrideComponentUiPackage" translatable="false"></string>

View File

@@ -176,6 +176,7 @@
<uses-permission android:name="android.permission.SET_TIME_ZONE" />
<uses-permission android:name="android.permission.DISABLE_HIDDEN_API_CHECKS" />
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.BYPASS_ROLE_QUALIFICATION" />
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.STATUS_BAR" />

View File

@@ -21,11 +21,22 @@ import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
import android.app.SyncNotedAppOp;
import android.app.role.RoleManager;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.location.LocationManagerInternal;
import android.net.Uri;
import android.os.IBinder;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.HeptFunction;
@@ -35,6 +46,8 @@ import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.server.LocalServices;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -42,9 +55,21 @@ import java.util.concurrent.ConcurrentHashMap;
* This class defines policy for special behaviors around app ops.
*/
public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegate {
private static final String LOG_TAG = AppOpsPolicy.class.getName();
private static final String ACTIVITY_RECOGNITION_TAGS =
"android:activity_recognition_allow_listed_tags";
private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";";
@NonNull
private final Object mLock = new Object();
@NonNull
private final Context mContext;
@NonNull
private final RoleManager mRoleManager;
/**
* The locking policy around the location tags is a bit special. Since we want to
* avoid grabbing the lock on every op note we are taking the approach where the
@@ -60,48 +85,57 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>> mLocationTags =
new ConcurrentHashMap<>();
public AppOpsPolicy() {
@GuardedBy("mLock - writes only - see above")
@NonNull
private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>>
mActivityRecognitionTags = new ConcurrentHashMap<>();
public AppOpsPolicy(@NonNull Context context) {
mContext = context;
mRoleManager = mContext.getSystemService(RoleManager.class);
final LocationManagerInternal locationManagerInternal = LocalServices.getService(
LocationManagerInternal.class);
locationManagerInternal.setOnProviderLocationTagsChangeListener((providerTagInfo) -> {
synchronized (mLock) {
final int uid = providerTagInfo.getUid();
// We make a copy of the per UID state to limit our mutation to one
// operation in the underlying concurrent data structure.
ArrayMap<String, ArraySet<String>> uidTags = mLocationTags.get(uid);
if (uidTags != null) {
uidTags = new ArrayMap<>(uidTags);
}
final String packageName = providerTagInfo.getPackageName();
ArraySet<String> packageTags = (uidTags != null) ? uidTags.get(packageName) : null;
if (packageTags != null) {
packageTags = new ArraySet<>(packageTags);
}
final Set<String> providerTags = providerTagInfo.getTags();
if (providerTags != null && !providerTags.isEmpty()) {
if (packageTags != null) {
packageTags.clear();
packageTags.addAll(providerTags);
} else {
packageTags = new ArraySet<>(providerTags);
}
if (uidTags == null) {
uidTags = new ArrayMap<>();
}
uidTags.put(packageName, packageTags);
mLocationTags.put(uid, uidTags);
} else if (uidTags != null) {
uidTags.remove(packageName);
if (!uidTags.isEmpty()) {
mLocationTags.put(uid, uidTags);
} else {
mLocationTags.remove(uid);
}
}
updateAllowListedTagsForPackageLocked(providerTagInfo.getUid(),
providerTagInfo.getPackageName(), providerTagInfo.getTags(),
mLocationTags);
}
});
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
intentFilter.addDataScheme("package");
context.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final Uri uri = intent.getData();
if (uri == null) {
return;
}
final String packageName = uri.getSchemeSpecificPart();
if (TextUtils.isEmpty(packageName)) {
return;
}
final List<String> activityRecognizers = mRoleManager.getRoleHolders(
RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER);
if (activityRecognizers.contains(packageName)) {
updateActivityRecognizerTags(packageName);
}
}
}, UserHandle.SYSTEM, intentFilter, null, null);
mRoleManager.addOnRoleHoldersChangedListenerAsUser(context.getMainExecutor(),
(String roleName, UserHandle user) -> {
if (RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER.equals(roleName)) {
initializeActivityRecognizersTags();
}
}, UserHandle.SYSTEM);
initializeActivityRecognizersTags();
}
@Override
@@ -121,7 +155,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
@Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable
String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer,
String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
return superImpl.apply(resolveOpCode(code, uid, packageName, attributionTag), uid,
return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), uid,
packageName, attributionTag, shouldCollectAsyncNotedOp,
message, shouldCollectMessage);
}
@@ -132,7 +166,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction<Integer,
AttributionSource, Boolean, String, Boolean, Boolean,
SyncNotedAppOp> superImpl) {
return superImpl.apply(resolveOpCode(code, attributionSource.getUid(),
return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skipProxyOperation);
@@ -144,7 +178,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @NonNull OctFunction<IBinder, Integer, AttributionSource,
Boolean, Boolean, String, Boolean, Boolean, SyncNotedAppOp> superImpl) {
return superImpl.apply(token, resolveOpCode(code, attributionSource.getUid(),
return superImpl.apply(token, resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation);
@@ -154,36 +188,129 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
public void finishProxyOperation(IBinder clientId, int code,
@NonNull AttributionSource attributionSource,
@NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl) {
superImpl.apply(clientId, resolveOpCode(code, attributionSource.getUid(),
superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource);
}
private int resolveOpCode(int code, int uid, @NonNull String packageName,
private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag) {
if (isHandledOp(code) && attributionTag != null) {
// Only a single lookup from the underlying concurrent data structure
final ArrayMap<String, ArraySet<String>> uidTags = mLocationTags.get(uid);
if (uidTags != null) {
final ArraySet<String> packageTags = uidTags.get(packageName);
if (packageTags != null && packageTags.contains(attributionTag)) {
return resolveHandledOp(code);
if (attributionTag == null) {
return code;
}
int resolvedCode = resolveLocationOp(code);
if (resolvedCode != code) {
if (isDatasourceAttributionTag(uid, packageName, attributionTag,
mLocationTags)) {
return resolvedCode;
}
} else {
resolvedCode = resolveArOp(code);
if (resolvedCode != code) {
if (isDatasourceAttributionTag(uid, packageName, attributionTag,
mActivityRecognitionTags)) {
return resolvedCode;
}
}
}
return code;
}
private static boolean isHandledOp(int code) {
switch (code) {
case AppOpsManager.OP_FINE_LOCATION:
case AppOpsManager.OP_COARSE_LOCATION:
private void initializeActivityRecognizersTags() {
final List<String> activityRecognizers = mRoleManager.getRoleHolders(
RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER);
final int recognizerCount = activityRecognizers.size();
if (recognizerCount > 0) {
for (int i = 0; i < recognizerCount; i++) {
final String activityRecognizer = activityRecognizers.get(i);
updateActivityRecognizerTags(activityRecognizer);
}
} else {
clearActivityRecognitionTags();
}
}
private void clearActivityRecognitionTags() {
synchronized (mLock) {
mActivityRecognitionTags.clear();
}
}
private void updateActivityRecognizerTags(@NonNull String activityRecognizer) {
try {
final ApplicationInfo recognizerAppInfo = mContext.getPackageManager()
.getApplicationInfoAsUser(activityRecognizer, PackageManager.GET_META_DATA,
UserHandle.USER_SYSTEM);
if (recognizerAppInfo.metaData == null) {
return;
}
final String tagsList = recognizerAppInfo.metaData.getString(ACTIVITY_RECOGNITION_TAGS);
if (tagsList != null) {
final String[] tags = tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR);
synchronized (mLock) {
updateAllowListedTagsForPackageLocked(recognizerAppInfo.uid,
recognizerAppInfo.packageName, new ArraySet<>(tags),
mActivityRecognitionTags);
}
}
} catch (PackageManager.NameNotFoundException e) {
Slog.wtf(LOG_TAG, "Missing " + RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER
+ " role holder package " + activityRecognizer);
}
}
private static void updateAllowListedTagsForPackageLocked(int uid, String packageName,
Set<String> allowListedTags, ConcurrentHashMap<Integer, ArrayMap<String,
ArraySet<String>>> datastore) {
// We make a copy of the per UID state to limit our mutation to one
// operation in the underlying concurrent data structure.
ArrayMap<String, ArraySet<String>> uidTags = datastore.get(uid);
if (uidTags != null) {
uidTags = new ArrayMap<>(uidTags);
}
ArraySet<String> packageTags = (uidTags != null) ? uidTags.get(packageName) : null;
if (packageTags != null) {
packageTags = new ArraySet<>(packageTags);
}
if (allowListedTags != null && !allowListedTags.isEmpty()) {
if (packageTags != null) {
packageTags.clear();
packageTags.addAll(allowListedTags);
} else {
packageTags = new ArraySet<>(allowListedTags);
}
if (uidTags == null) {
uidTags = new ArrayMap<>();
}
uidTags.put(packageName, packageTags);
datastore.put(uid, uidTags);
} else if (uidTags != null) {
uidTags.remove(packageName);
if (!uidTags.isEmpty()) {
datastore.put(uid, uidTags);
} else {
datastore.remove(uid);
}
}
}
private static boolean isDatasourceAttributionTag(int uid, @NonNull String packageName,
@NonNull String attributionTag, @NonNull Map<Integer, ArrayMap<String,
ArraySet<String>>> mappedOps) {
// Only a single lookup from the underlying concurrent data structure
final ArrayMap<String, ArraySet<String>> uidTags = mappedOps.get(uid);
if (uidTags != null) {
final ArraySet<String> packageTags = uidTags.get(packageName);
if (packageTags != null && packageTags.contains(attributionTag)) {
return true;
}
}
return false;
}
private static int resolveHandledOp(int code) {
private static int resolveLocationOp(int code) {
switch (code) {
case AppOpsManager.OP_FINE_LOCATION:
return AppOpsManager.OP_FINE_LOCATION_SOURCE;
@@ -192,4 +319,11 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
}
return code;
}
private static int resolveArOp(int code) {
if (code == AppOpsManager.OP_ACTIVITY_RECOGNITION) {
return AppOpsManager.OP_ACTIVITY_RECOGNITION_SOURCE;
}
return code;
}
}

View File

@@ -2678,7 +2678,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("RegisterAppOpsPolicy");
try {
mActivityManagerService.setAppOpsPolicy(new AppOpsPolicy());
mActivityManagerService.setAppOpsPolicy(new AppOpsPolicy(mSystemContext));
} catch (Throwable e) {
reportWtf("registering app ops policy", e);
}