1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.policy; 18 19 import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; 20 import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.AppOpsManager; 25 import android.app.AppOpsManager.AttributionFlags; 26 import android.app.AppOpsManagerInternal; 27 import android.app.SyncNotedAppOp; 28 import android.app.role.RoleManager; 29 import android.content.AttributionSource; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.PackageManager; 35 import android.content.pm.ResolveInfo; 36 import android.location.LocationManagerInternal; 37 import android.net.Uri; 38 import android.os.Binder; 39 import android.os.Bundle; 40 import android.os.IBinder; 41 import android.os.PackageTagsList; 42 import android.os.Process; 43 import android.os.SystemProperties; 44 import android.os.UserHandle; 45 import android.permission.flags.Flags; 46 import android.service.voice.VoiceInteractionManagerInternal; 47 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; 48 import android.text.TextUtils; 49 import android.util.Log; 50 import android.util.SparseArray; 51 52 import com.android.internal.annotations.GuardedBy; 53 import com.android.internal.util.function.DodecFunction; 54 import com.android.internal.util.function.HexConsumer; 55 import com.android.internal.util.function.HexFunction; 56 import com.android.internal.util.function.NonaFunction; 57 import com.android.internal.util.function.QuadFunction; 58 import com.android.internal.util.function.UndecFunction; 59 import com.android.server.LocalServices; 60 61 import java.io.PrintWriter; 62 import java.util.Arrays; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.concurrent.ConcurrentHashMap; 66 67 /** 68 * This class defines policy for special behaviors around app ops. 69 */ 70 public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegate { 71 private static final String LOG_TAG = AppOpsPolicy.class.getName(); 72 73 private static final String ACTIVITY_RECOGNITION_TAGS = 74 "android:activity_recognition_allow_listed_tags"; 75 private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";"; 76 private static final boolean SYSPROP_HOTWORD_DETECTION_SERVICE_REQUIRED = 77 SystemProperties.getBoolean("ro.hotword.detection_service_required", false); 78 79 @NonNull 80 private final Object mLock = new Object(); 81 82 @NonNull 83 private final IBinder mToken = new Binder(); 84 85 @NonNull 86 private final Context mContext; 87 88 @NonNull 89 private final RoleManager mRoleManager; 90 91 @NonNull 92 private final VoiceInteractionManagerInternal mVoiceInteractionManagerInternal; 93 94 /** 95 * Whether this device allows only the HotwordDetectionService to use 96 * OP_RECORD_AUDIO_HOTWORD which doesn't incur the privacy indicator. 97 */ 98 private final boolean mIsHotwordDetectionServiceRequired; 99 100 /** 101 * The locking policy around the location tags is a bit special. Since we want to 102 * avoid grabbing the lock on every op note we are taking the approach where the 103 * read and write are being done via a thread-safe data structure such that the 104 * lookup/insert are single thread-safe calls. When we update the cached state we 105 * use a lock to ensure the update's lookup and store calls are done atomically, 106 * so multiple writers would not interleave. The tradeoff is we make is that the 107 * concurrent data structure would use boxing/unboxing of integers but this is 108 * preferred to locking. 109 */ 110 @GuardedBy("mLock - writes only - see above") 111 @NonNull 112 private final ConcurrentHashMap<Integer, PackageTagsList> mLocationTags = 113 new ConcurrentHashMap<>(); 114 115 // location tags can vary per uid - but we merge all tags under an app id into the final data 116 // structure above 117 @GuardedBy("mLock") 118 private final SparseArray<PackageTagsList> mPerUidLocationTags = new SparseArray<>(); 119 120 // activity recognition currently only grabs tags from the APK manifest. we know that the 121 // manifest is the same for all users, so there's no need to track variations in tags across 122 // different users. if that logic ever changes, this might need to behave more like location 123 // tags above. 124 @GuardedBy("mLock - writes only - see above") 125 @NonNull 126 private final ConcurrentHashMap<Integer, PackageTagsList> mActivityRecognitionTags = 127 new ConcurrentHashMap<>(); 128 AppOpsPolicy(@onNull Context context)129 public AppOpsPolicy(@NonNull Context context) { 130 mContext = context; 131 mRoleManager = mContext.getSystemService(RoleManager.class); 132 mVoiceInteractionManagerInternal = LocalServices.getService( 133 VoiceInteractionManagerInternal.class); 134 mIsHotwordDetectionServiceRequired = isHotwordDetectionServiceRequired( 135 mContext.getPackageManager()); 136 137 final LocationManagerInternal locationManagerInternal = LocalServices.getService( 138 LocationManagerInternal.class); 139 if (locationManagerInternal != null) { 140 locationManagerInternal.setLocationPackageTagsListener( 141 (uid, packageTagsList) -> { 142 synchronized (mLock) { 143 if (packageTagsList.isEmpty()) { 144 mPerUidLocationTags.remove(uid); 145 } else { 146 mPerUidLocationTags.set(uid, packageTagsList); 147 } 148 149 int appId = UserHandle.getAppId(uid); 150 PackageTagsList.Builder appIdTags = new PackageTagsList.Builder(1); 151 int size = mPerUidLocationTags.size(); 152 for (int i = 0; i < size; i++) { 153 if (UserHandle.getAppId(mPerUidLocationTags.keyAt(i)) == appId) { 154 appIdTags.add(mPerUidLocationTags.valueAt(i)); 155 } 156 } 157 158 updateAllowListedTagsForPackageLocked(appId, appIdTags.build(), 159 mLocationTags); 160 } 161 }); 162 } 163 164 final IntentFilter intentFilter = new IntentFilter(); 165 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 166 intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); 167 intentFilter.addDataScheme("package"); 168 169 context.registerReceiverAsUser(new BroadcastReceiver() { 170 @Override 171 public void onReceive(Context context, Intent intent) { 172 final Uri uri = intent.getData(); 173 if (uri == null) { 174 return; 175 } 176 final String packageName = uri.getSchemeSpecificPart(); 177 if (TextUtils.isEmpty(packageName)) { 178 return; 179 } 180 final List<String> activityRecognizers = mRoleManager.getRoleHolders( 181 RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER); 182 if (activityRecognizers.contains(packageName)) { 183 updateActivityRecognizerTags(packageName); 184 } 185 } 186 }, UserHandle.SYSTEM, intentFilter, null, null); 187 188 mRoleManager.addOnRoleHoldersChangedListenerAsUser(context.getMainExecutor(), 189 (String roleName, UserHandle user) -> { 190 if (RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER.equals(roleName)) { 191 initializeActivityRecognizersTags(); 192 } 193 }, UserHandle.SYSTEM); 194 195 initializeActivityRecognizersTags(); 196 197 // Restrict phone call ops if the TelecomService will not start (conditioned on having 198 // FEATURE_MICROPHONE, FEATURE_TELECOM, or FEATURE_TELEPHONY). 199 PackageManager pm = mContext.getPackageManager(); 200 if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) 201 && !pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) 202 && !pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)) { 203 AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); 204 appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true, mToken, 205 null, UserHandle.USER_ALL); 206 appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_CAMERA, true, mToken, 207 null, UserHandle.USER_ALL); 208 } 209 } 210 211 /** 212 * @return the op that should be noted for the voice activations of the app by detected hotword. 213 */ getVoiceActivationOp()214 public static int getVoiceActivationOp() { 215 if (Flags.voiceActivationPermissionApis()) { 216 return OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; 217 } 218 return OP_RECORD_AUDIO_HOTWORD; 219 } 220 221 /** 222 * @hide 223 */ isHotwordDetectionServiceRequired(PackageManager pm)224 public static boolean isHotwordDetectionServiceRequired(PackageManager pm) { 225 // The HotwordDetectionService APIs aren't ready yet for Auto or TV. 226 if (pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) 227 || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 228 return false; 229 } 230 return SYSPROP_HOTWORD_DETECTION_SERVICE_REQUIRED; 231 } 232 233 @Override checkOperation(int code, int uid, String packageName, @Nullable String attributionTag, int virtualDeviceId, boolean raw, HexFunction<Integer, Integer, String, String, Integer, Boolean, Integer> superImpl)234 public int checkOperation(int code, int uid, String packageName, 235 @Nullable String attributionTag, int virtualDeviceId, boolean raw, 236 HexFunction<Integer, Integer, String, String, Integer, Boolean, Integer> superImpl) { 237 return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, 238 virtualDeviceId, raw); 239 } 240 241 @Override checkAudioOperation(int code, int usage, int uid, String packageName, QuadFunction<Integer, Integer, Integer, String, Integer> superImpl)242 public int checkAudioOperation(int code, int usage, int uid, String packageName, 243 QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) { 244 return superImpl.apply(code, usage, uid, packageName); 245 } 246 247 @Override noteOperation(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, int notedCount, @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String, Boolean, Integer, SyncNotedAppOp> superImpl)248 public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName, 249 @Nullable String attributionTag, int virtualDeviceId, 250 boolean shouldCollectAsyncNotedOp, @Nullable String message, 251 boolean shouldCollectMessage, int notedCount, 252 @NonNull NonaFunction<Integer, Integer, String, String, 253 Integer, Boolean, String, Boolean, Integer, SyncNotedAppOp> superImpl) { 254 return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), 255 resolveUid(code, uid), packageName, attributionTag, virtualDeviceId, 256 shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount); 257 } 258 259 @Override noteProxyOperation(int code, @NonNull AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean, Boolean, SyncNotedAppOp> superImpl)260 public SyncNotedAppOp noteProxyOperation(int code, @NonNull AttributionSource attributionSource, 261 boolean shouldCollectAsyncNotedOp, @Nullable String message, 262 boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction<Integer, 263 AttributionSource, Boolean, String, Boolean, Boolean, 264 SyncNotedAppOp> superImpl) { 265 return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(), 266 attributionSource.getPackageName(), attributionSource.getAttributionTag()), 267 attributionSource, shouldCollectAsyncNotedOp, message, shouldCollectMessage, 268 skipProxyOperation); 269 } 270 271 @Override startOperation(IBinder token, int code, int uid, @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, int attributionChainId, @NonNull DodecFunction<IBinder, Integer, Integer, String, String, Integer, Boolean, Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl)272 public SyncNotedAppOp startOperation(IBinder token, int code, int uid, 273 @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, 274 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, 275 boolean shouldCollectMessage, @AttributionFlags int attributionFlags, 276 int attributionChainId, @NonNull DodecFunction<IBinder, Integer, Integer, String, 277 String, Integer, Boolean, Boolean, String, Boolean, Integer, Integer, 278 SyncNotedAppOp> superImpl) { 279 return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag), 280 resolveUid(code, uid), packageName, attributionTag, virtualDeviceId, 281 startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, 282 attributionFlags, attributionChainId); 283 } 284 285 @Override startProxyOperation(@onNull IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags, int attributionChainId, @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String, Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl)286 public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code, 287 @NonNull AttributionSource attributionSource, boolean startIfModeDefault, 288 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, 289 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, 290 @AttributionFlags int proxiedAttributionFlags, int attributionChainId, 291 @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String, 292 Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) { 293 return superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(), 294 attributionSource.getPackageName(), attributionSource.getAttributionTag()), 295 attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, 296 shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, 297 proxiedAttributionFlags, attributionChainId); 298 } 299 300 @Override finishOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag, int virtualDeviceId, @NonNull HexConsumer<IBinder, Integer, Integer, String, String, Integer> superImpl)301 public void finishOperation(IBinder clientId, int code, int uid, String packageName, 302 String attributionTag, int virtualDeviceId, 303 @NonNull HexConsumer<IBinder, Integer, Integer, String, String, Integer> superImpl) { 304 superImpl.accept(clientId, resolveDatasourceOp(code, uid, packageName, attributionTag), 305 resolveUid(code, uid), packageName, attributionTag, virtualDeviceId); 306 } 307 308 @Override finishProxyOperation(@onNull IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation, @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl)309 public void finishProxyOperation(@NonNull IBinder clientId, int code, 310 @NonNull AttributionSource attributionSource, boolean skipProxyOperation, 311 @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl) { 312 superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(), 313 attributionSource.getPackageName(), attributionSource.getAttributionTag()), 314 attributionSource, skipProxyOperation); 315 } 316 317 /** 318 * Write location and activity recognition tags to console. 319 * See also {@code adb shell dumpsys appops}. 320 */ dumpTags(PrintWriter writer)321 public void dumpTags(PrintWriter writer) { 322 if (!mLocationTags.isEmpty()) { 323 writer.println(" AppOps policy location tags:"); 324 writeTags(mLocationTags, writer); 325 writer.println(); 326 } 327 if (!mActivityRecognitionTags.isEmpty()) { 328 writer.println(" AppOps policy activity recognition tags:"); 329 writeTags(mActivityRecognitionTags, writer); 330 writer.println(); 331 } 332 } 333 writeTags(Map<Integer, PackageTagsList> tags, PrintWriter writer)334 private void writeTags(Map<Integer, PackageTagsList> tags, PrintWriter writer) { 335 int counter = 0; 336 for (Map.Entry<Integer, PackageTagsList> tagEntry : tags.entrySet()) { 337 writer.print(" #"); writer.print(counter++); writer.print(": "); 338 writer.print(tagEntry.getKey().toString()); writer.print("="); 339 tagEntry.getValue().dump(writer); 340 } 341 } 342 resolveDatasourceOp(int code, int uid, @NonNull String packageName, @Nullable String attributionTag)343 private int resolveDatasourceOp(int code, int uid, @NonNull String packageName, 344 @Nullable String attributionTag) { 345 code = resolveRecordAudioOp(code, uid); 346 code = resolveSandboxedServiceOp(code, uid); 347 if (attributionTag == null) { 348 return code; 349 } 350 int resolvedCode = resolveLocationOp(code); 351 if (resolvedCode != code) { 352 if (isDatasourceAttributionTag(uid, packageName, attributionTag, 353 mLocationTags)) { 354 return resolvedCode; 355 } 356 } else { 357 resolvedCode = resolveArOp(code); 358 if (resolvedCode != code) { 359 if (isDatasourceAttributionTag(uid, packageName, attributionTag, 360 mActivityRecognitionTags)) { 361 return resolvedCode; 362 } 363 } 364 } 365 return code; 366 } 367 initializeActivityRecognizersTags()368 private void initializeActivityRecognizersTags() { 369 final List<String> activityRecognizers = mRoleManager.getRoleHolders( 370 RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER); 371 final int recognizerCount = activityRecognizers.size(); 372 if (recognizerCount > 0) { 373 for (int i = 0; i < recognizerCount; i++) { 374 final String activityRecognizer = activityRecognizers.get(i); 375 updateActivityRecognizerTags(activityRecognizer); 376 } 377 } else { 378 clearActivityRecognitionTags(); 379 } 380 } 381 clearActivityRecognitionTags()382 private void clearActivityRecognitionTags() { 383 synchronized (mLock) { 384 mActivityRecognitionTags.clear(); 385 } 386 } 387 updateActivityRecognizerTags(@onNull String activityRecognizer)388 private void updateActivityRecognizerTags(@NonNull String activityRecognizer) { 389 final int flags = PackageManager.GET_SERVICES 390 | PackageManager.GET_META_DATA 391 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS 392 | PackageManager.MATCH_DIRECT_BOOT_AWARE 393 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 394 395 final Intent intent = new Intent(Intent.ACTION_ACTIVITY_RECOGNIZER); 396 intent.setPackage(activityRecognizer); 397 final ResolveInfo resolvedService = mContext.getPackageManager() 398 .resolveServiceAsUser(intent, flags, UserHandle.USER_SYSTEM); 399 if (resolvedService == null || resolvedService.serviceInfo == null) { 400 Log.w(LOG_TAG, "Service recognizer doesn't handle " 401 + Intent.ACTION_ACTIVITY_RECOGNIZER + ", ignoring!"); 402 return; 403 } 404 final Bundle metaData = resolvedService.serviceInfo.metaData; 405 if (metaData == null) { 406 return; 407 } 408 final String tagsList = metaData.getString(ACTIVITY_RECOGNITION_TAGS); 409 if (!TextUtils.isEmpty(tagsList)) { 410 PackageTagsList packageTagsList = new PackageTagsList.Builder(1).add( 411 resolvedService.serviceInfo.packageName, 412 Arrays.asList(tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR))).build(); 413 synchronized (mLock) { 414 updateAllowListedTagsForPackageLocked( 415 UserHandle.getAppId(resolvedService.serviceInfo.applicationInfo.uid), 416 packageTagsList, 417 mActivityRecognitionTags); 418 } 419 } 420 } 421 updateAllowListedTagsForPackageLocked(int appId, PackageTagsList packageTagsList, ConcurrentHashMap<Integer, PackageTagsList> datastore)422 private static void updateAllowListedTagsForPackageLocked(int appId, 423 PackageTagsList packageTagsList, 424 ConcurrentHashMap<Integer, PackageTagsList> datastore) { 425 datastore.put(appId, packageTagsList); 426 } 427 isDatasourceAttributionTag(int uid, @NonNull String packageName, @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps)428 private static boolean isDatasourceAttributionTag(int uid, @NonNull String packageName, 429 @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps) { 430 // Only a single lookup from the underlying concurrent data structure 431 final PackageTagsList appIdTags = mappedOps.get(UserHandle.getAppId(uid)); 432 return appIdTags != null && appIdTags.contains(packageName, attributionTag); 433 } 434 resolveLocationOp(int code)435 private static int resolveLocationOp(int code) { 436 switch (code) { 437 case AppOpsManager.OP_FINE_LOCATION: 438 return AppOpsManager.OP_FINE_LOCATION_SOURCE; 439 case AppOpsManager.OP_COARSE_LOCATION: 440 return AppOpsManager.OP_COARSE_LOCATION_SOURCE; 441 } 442 return code; 443 } 444 resolveArOp(int code)445 private static int resolveArOp(int code) { 446 if (code == AppOpsManager.OP_ACTIVITY_RECOGNITION) { 447 return AppOpsManager.OP_ACTIVITY_RECOGNITION_SOURCE; 448 } 449 return code; 450 } 451 resolveRecordAudioOp(int code, int uid)452 private int resolveRecordAudioOp(int code, int uid) { 453 if (code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD) { 454 if (!mIsHotwordDetectionServiceRequired) { 455 return code; 456 } 457 // Only the HotwordDetectionService can use the RECORD_AUDIO_HOTWORD op which doesn't 458 // incur the privacy indicator. Downgrade to standard RECORD_AUDIO for other processes. 459 final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = 460 mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); 461 if (hotwordDetectionServiceIdentity != null 462 && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { 463 return code; 464 } 465 return AppOpsManager.OP_RECORD_AUDIO; 466 } 467 return code; 468 } 469 resolveSandboxedServiceOp(int code, int uid)470 private int resolveSandboxedServiceOp(int code, int uid) { 471 if (!Process.isIsolated(uid) // simple check which fails-fast for the common case 472 || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) { 473 return code; 474 } 475 final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = 476 mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); 477 if (hotwordDetectionServiceIdentity != null 478 && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { 479 // Upgrade the op such that no indicators is shown for camera or audio service. This 480 // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA. 481 switch (code) { 482 case AppOpsManager.OP_RECORD_AUDIO: 483 return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED; 484 case AppOpsManager.OP_CAMERA: 485 return AppOpsManager.OP_CAMERA_SANDBOXED; 486 } 487 } 488 return code; 489 } 490 491 resolveUid(int code, int uid)492 private int resolveUid(int code, int uid) { 493 // The HotwordDetectionService is an isolated service, which ordinarily cannot hold 494 // permissions. So we allow it to assume the owning package identity for certain 495 // operations. 496 // Note: The package name coming from the audio server is already the one for the owning 497 // package, so we don't need to modify it. 498 if (Process.isIsolated(uid) // simple check which fails-fast for the common case 499 && (code == AppOpsManager.OP_RECORD_AUDIO 500 || code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD 501 || code == AppOpsManager.OP_CAMERA)) { 502 final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = 503 mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); 504 if (hotwordDetectionServiceIdentity != null 505 && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { 506 uid = hotwordDetectionServiceIdentity.getOwnerUid(); 507 } 508 } 509 return uid; 510 } 511 } 512