1 /* 2 * Copyright (C) 2024 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.ecm; 18 19 import static android.app.ecm.EnhancedConfirmationManager.REASON_PACKAGE_RESTRICTED; 20 import static android.app.ecm.EnhancedConfirmationManager.REASON_PHONE_STATE; 21 22 import static com.android.permission.PermissionStatsLog.CALL_WITH_ECM_INTERACTION_REPORTED; 23 import static com.android.permissioncontroller.PermissionControllerStatsLog.ECM_RESTRICTION_QUERY_IN_CALL_REPORTED; 24 25 import android.Manifest; 26 import android.accessibilityservice.AccessibilityServiceInfo; 27 import android.annotation.FlaggedApi; 28 import android.annotation.IntDef; 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.annotation.SuppressLint; 32 import android.annotation.UserIdInt; 33 import android.annotation.WorkerThread; 34 import android.app.AppOpsManager; 35 import android.app.ecm.EnhancedConfirmationManager; 36 import android.app.ecm.IEnhancedConfirmationManager; 37 import android.app.role.RoleManager; 38 import android.content.ContentResolver; 39 import android.content.Context; 40 import android.content.pm.ApplicationInfo; 41 import android.content.pm.InstallSourceInfo; 42 import android.content.pm.PackageInstaller; 43 import android.content.pm.PackageManager; 44 import android.content.pm.PackageManager.NameNotFoundException; 45 import android.content.pm.SignedPackage; 46 import android.database.Cursor; 47 import android.net.Uri; 48 import android.os.Binder; 49 import android.os.Build; 50 import android.os.Looper; 51 import android.os.SystemClock; 52 import android.os.SystemConfigManager; 53 import android.os.UserHandle; 54 import android.permission.flags.Flags; 55 import android.provider.ContactsContract; 56 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 57 import android.provider.ContactsContract.PhoneLookup; 58 import android.telecom.Call; 59 import android.telecom.PhoneAccount; 60 import android.telephony.TelephonyManager; 61 import android.util.ArrayMap; 62 import android.util.ArraySet; 63 import android.util.Log; 64 import android.view.accessibility.AccessibilityManager; 65 66 import androidx.annotation.Keep; 67 import androidx.annotation.RequiresApi; 68 69 import com.android.internal.annotations.GuardedBy; 70 import com.android.internal.util.Preconditions; 71 import com.android.permission.util.UserUtils; 72 import com.android.permissioncontroller.PermissionControllerStatsLog; 73 import com.android.server.LocalManagerRegistry; 74 import com.android.server.SystemService; 75 76 import java.lang.annotation.Retention; 77 import java.lang.annotation.RetentionPolicy; 78 import java.util.ArrayList; 79 import java.util.List; 80 import java.util.Map; 81 import java.util.Objects; 82 import java.util.Set; 83 import java.util.concurrent.ConcurrentHashMap; 84 import java.util.concurrent.TimeUnit; 85 86 87 /** 88 * Service for ECM (Enhanced Confirmation Mode). 89 * 90 * @see EnhancedConfirmationManager 91 * 92 * @hide 93 */ 94 @Keep 95 @FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 96 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 97 @SuppressLint("MissingPermission") 98 public class EnhancedConfirmationService extends SystemService { 99 private static final String LOG_TAG = EnhancedConfirmationService.class.getSimpleName(); 100 101 /** A map of ECM states to their corresponding app op states */ 102 @Retention(java.lang.annotation.RetentionPolicy.SOURCE) 103 @IntDef(prefix = {"ECM_STATE_"}, value = {EcmState.ECM_STATE_NOT_GUARDED, 104 EcmState.ECM_STATE_GUARDED, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED, 105 EcmState.ECM_STATE_IMPLICIT}) 106 private @interface EcmState { 107 int ECM_STATE_NOT_GUARDED = AppOpsManager.MODE_ALLOWED; 108 int ECM_STATE_GUARDED = AppOpsManager.MODE_ERRORED; 109 int ECM_STATE_GUARDED_AND_ACKNOWLEDGED = AppOpsManager.MODE_IGNORED; 110 int ECM_STATE_IMPLICIT = AppOpsManager.MODE_DEFAULT; 111 } 112 113 private static final ArraySet<String> PER_PACKAGE_PROTECTED_SETTINGS = new ArraySet<>(); 114 115 // Settings restricted when an untrusted call is ongoing. These must also be added to 116 // PROTECTED_SETTINGS 117 private static final ArraySet<String> UNTRUSTED_CALL_RESTRICTED_SETTINGS = new ArraySet<>(); 118 119 static { 120 // Runtime permissions 121 PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.SEND_SMS); 122 PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_SMS); 123 PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.READ_SMS); 124 PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_MMS); 125 PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_WAP_PUSH); 126 PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.READ_CELL_BROADCASTS); 127 PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission_group.SMS); 128 129 PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.BIND_DEVICE_ADMIN); 130 // App ops 131 PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); 132 PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS); 133 PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW); 134 PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_GET_USAGE_STATS); 135 PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS); 136 // Default application roles. 137 PER_PACKAGE_PROTECTED_SETTINGS.add(RoleManager.ROLE_DIALER); 138 PER_PACKAGE_PROTECTED_SETTINGS.add(RoleManager.ROLE_SMS); 139 140 if (Flags.unknownCallPackageInstallBlockingEnabled()) { 141 // Requesting package installs, limited during phone calls 142 UNTRUSTED_CALL_RESTRICTED_SETTINGS.add( 143 AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES); 144 UNTRUSTED_CALL_RESTRICTED_SETTINGS.add( 145 AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); 146 } 147 } 148 149 private Map<String, List<byte[]>> mTrustedPackageCertDigests; 150 private Map<String, List<byte[]>> mTrustedInstallerCertDigests; 151 private static final int CALL_TYPE_UNTRUSTED = 0; 152 private static final int CALL_TYPE_TRUSTED = 1; 153 private static final int CALL_TYPE_EMERGENCY = 1 << 1; 154 @IntDef(flag = true, value = { 155 CALL_TYPE_UNTRUSTED, 156 CALL_TYPE_TRUSTED, 157 CALL_TYPE_EMERGENCY 158 }) 159 @Retention(RetentionPolicy.SOURCE) 160 @interface CallType {} 161 EnhancedConfirmationService(@onNull Context context)162 public EnhancedConfirmationService(@NonNull Context context) { 163 super(context); 164 LocalManagerRegistry.addManager(EnhancedConfirmationManagerLocal.class, 165 new EnhancedConfirmationManagerLocalImpl(this)); 166 } 167 168 private PackageManager mPackageManager; 169 170 // A helper which tracks the calls received by the system, and information about them. 171 private CallTracker mCallTracker; 172 173 @GuardedBy("mUserAccessibilityManagers") 174 private final Map<Integer, AccessibilityManager> mUserAccessibilityManagers = 175 new ArrayMap<>(); 176 177 @Override onStart()178 public void onStart() { 179 Context context = getContext(); 180 SystemConfigManager systemConfigManager = context.getSystemService( 181 SystemConfigManager.class); 182 mTrustedPackageCertDigests = toTrustedPackageMap( 183 systemConfigManager.getEnhancedConfirmationTrustedPackages()); 184 mTrustedInstallerCertDigests = toTrustedPackageMap( 185 systemConfigManager.getEnhancedConfirmationTrustedInstallers()); 186 187 publishBinderService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE, new Stub()); 188 189 if (Flags.unknownCallPackageInstallBlockingEnabled()) { 190 mCallTracker = new CallTracker(getContext()); 191 } 192 mPackageManager = getContext().getPackageManager(); 193 } 194 toTrustedPackageMap(Set<SignedPackage> signedPackages)195 private Map<String, List<byte[]>> toTrustedPackageMap(Set<SignedPackage> signedPackages) { 196 ArrayMap<String, List<byte[]>> trustedPackageMap = new ArrayMap<>(); 197 for (SignedPackage signedPackage : signedPackages) { 198 ArrayList<byte[]> certDigests = (ArrayList<byte[]>) trustedPackageMap.computeIfAbsent( 199 signedPackage.getPackageName(), packageName -> new ArrayList<>(1)); 200 certDigests.add(signedPackage.getCertificateDigest()); 201 } 202 return trustedPackageMap; 203 } 204 addOngoingCall(Call call)205 void addOngoingCall(Call call) { 206 assertNotMainThread(); 207 if (mCallTracker != null) { 208 mCallTracker.addCall(call); 209 } 210 } 211 212 @WorkerThread removeOngoingCall(String callId)213 void removeOngoingCall(String callId) { 214 assertNotMainThread(); 215 if (mCallTracker != null) { 216 mCallTracker.endCall(callId); 217 } 218 } 219 220 @WorkerThread clearOngoingCalls()221 void clearOngoingCalls() { 222 assertNotMainThread(); 223 224 if (mCallTracker != null) { 225 mCallTracker.endAllCalls(); 226 } 227 } 228 getPackageUid(PackageManager pm, String packageName, int userId)229 static int getPackageUid(PackageManager pm, String packageName, 230 int userId) throws NameNotFoundException { 231 return pm.getPackageUidAsUser(packageName, PackageManager.PackageInfoFlags.of(0), userId); 232 } 233 assertNotMainThread()234 private void assertNotMainThread() throws IllegalStateException { 235 if (Looper.myLooper() == Looper.getMainLooper()) { 236 throw new IllegalStateException("Ecm WorkerThread method called on main thread"); 237 } 238 } 239 240 private class Stub extends IEnhancedConfirmationManager.Stub { 241 242 private final @NonNull Context mContext; 243 private final String mAttributionTag; 244 private final AppOpsManager mAppOpsManager; 245 Stub()246 Stub() { 247 Context context = getContext(); 248 mContext = context; 249 mAttributionTag = context.getAttributionTag(); 250 mAppOpsManager = context.getSystemService(AppOpsManager.class); 251 } 252 isRestricted(@onNull String packageName, @NonNull String settingIdentifier, @UserIdInt int userId)253 public boolean isRestricted(@NonNull String packageName, @NonNull String settingIdentifier, 254 @UserIdInt int userId) { 255 return getRestrictionReason(packageName, settingIdentifier, userId) != null; 256 } 257 getRestrictionReason(@onNull String packageName, @NonNull String settingIdentifier, @UserIdInt int userId)258 public String getRestrictionReason(@NonNull String packageName, 259 @NonNull String settingIdentifier, 260 @UserIdInt int userId) { 261 enforcePermissions("isRestricted", userId); 262 if (!UserUtils.isUserExistent(userId, getContext())) { 263 Log.e(LOG_TAG, "user " + userId + " does not exist"); 264 return null; 265 } 266 267 Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); 268 Preconditions.checkStringNotEmpty(settingIdentifier, 269 "settingIdentifier cannot be null or empty"); 270 271 try { 272 if (!isSettingEcmProtected(settingIdentifier)) { 273 return null; 274 } 275 if (isSettingEcmGuardedForPackage(settingIdentifier, packageName, userId)) { 276 return REASON_PACKAGE_RESTRICTED; 277 } 278 String globalProtectionReason = 279 getGlobalProtectionReason(settingIdentifier, packageName, userId); 280 if (globalProtectionReason != null) { 281 return globalProtectionReason; 282 } 283 return null; 284 } catch (NameNotFoundException e) { 285 throw new IllegalArgumentException(e); 286 } 287 } 288 clearRestriction(@onNull String packageName, @UserIdInt int userId)289 public void clearRestriction(@NonNull String packageName, @UserIdInt int userId) { 290 enforcePermissions("clearRestriction", userId); 291 if (!UserUtils.isUserExistent(userId, getContext())) { 292 return; 293 } 294 295 Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); 296 297 try { 298 int state = getAppEcmState(packageName, userId); 299 boolean isAllowed = state == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED; 300 if (!isAllowed) { 301 throw new IllegalStateException("Clear restriction attempted but not allowed"); 302 } 303 setAppEcmState(packageName, EcmState.ECM_STATE_NOT_GUARDED, userId); 304 EnhancedConfirmationStatsLogUtils.INSTANCE.logRestrictionCleared( 305 getPackageUid(mPackageManager, packageName, userId)); 306 } catch (NameNotFoundException e) { 307 throw new IllegalArgumentException(e); 308 } 309 } 310 isClearRestrictionAllowed(@onNull String packageName, @UserIdInt int userId)311 public boolean isClearRestrictionAllowed(@NonNull String packageName, 312 @UserIdInt int userId) { 313 enforcePermissions("isClearRestrictionAllowed", userId); 314 if (!UserUtils.isUserExistent(userId, getContext())) { 315 return false; 316 } 317 318 Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); 319 320 try { 321 int state = getAppEcmState(packageName, userId); 322 return state == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED; 323 } catch (NameNotFoundException e) { 324 throw new IllegalArgumentException(e); 325 } 326 } 327 setClearRestrictionAllowed(@onNull String packageName, @UserIdInt int userId)328 public void setClearRestrictionAllowed(@NonNull String packageName, @UserIdInt int userId) { 329 enforcePermissions("setClearRestrictionAllowed", userId); 330 if (!UserUtils.isUserExistent(userId, getContext())) { 331 return; 332 } 333 334 Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); 335 336 try { 337 if (isPackageEcmGuarded(packageName, userId)) { 338 setAppEcmState(packageName, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED, 339 userId); 340 } 341 } catch (NameNotFoundException e) { 342 throw new IllegalArgumentException(e); 343 } 344 } 345 enforcePermissions(@onNull String methodName, @UserIdInt int userId)346 private void enforcePermissions(@NonNull String methodName, @UserIdInt int userId) { 347 UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, 348 /* enforceForProfileGroup= */ false, methodName, mContext); 349 mContext.enforceCallingOrSelfPermission( 350 android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES, methodName); 351 } 352 isPackageEcmGuarded(@onNull String packageName, @UserIdInt int userId)353 private boolean isPackageEcmGuarded(@NonNull String packageName, @UserIdInt int userId) 354 throws NameNotFoundException { 355 ApplicationInfo applicationInfo = getApplicationInfoAsUser(packageName, userId); 356 // Always trust allow-listed and pre-installed packages 357 if (isAllowlistedPackage(packageName) || isAllowlistedInstaller(packageName) 358 || isPackagePreinstalled(applicationInfo)) { 359 return false; 360 } 361 362 // If the package already has an explicitly-set state, use that 363 @EcmState int ecmState = getAppEcmState(packageName, userId); 364 if (ecmState == EcmState.ECM_STATE_GUARDED 365 || ecmState == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED) { 366 return true; 367 } 368 if (ecmState == EcmState.ECM_STATE_NOT_GUARDED) { 369 return false; 370 } 371 372 // Otherwise, lazily decide whether the app is considered guarded. 373 InstallSourceInfo installSource; 374 try { 375 installSource = mContext.createContextAsUser(UserHandle.of(userId), 0) 376 .getPackageManager() 377 .getInstallSourceInfo(packageName); 378 } catch (NameNotFoundException e) { 379 Log.w(LOG_TAG, "Package not found: " + packageName); 380 return false; 381 } 382 383 // These install sources are always considered dangerous. 384 // PackageInstallers that are trusted can use these as a signal that the 385 // packages they've installed aren't as trusted as themselves. 386 int packageSource = installSource.getPackageSource(); 387 if (packageSource == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE 388 || packageSource == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { 389 return true; 390 } 391 String installingPackageName = installSource.getInstallingPackageName(); 392 ApplicationInfo installingApplicationInfo = 393 getApplicationInfoAsUser(installingPackageName, userId); 394 395 // ECM doesn't consider a transitive chain of trust for install sources. 396 // If this package hasn't been explicitly handled by this point 397 // then it is exempt from ECM if the immediate parent is a trusted installer 398 return !(trustPackagesInstalledViaNonAllowlistedInstallers() 399 || isPackagePreinstalled(installingApplicationInfo) 400 || isAllowlistedInstaller(installingPackageName)); 401 } 402 isSettingEcmGuardedForPackage(@onNull String settingIdentifier, @NonNull String packageName, @UserIdInt int userId)403 private boolean isSettingEcmGuardedForPackage(@NonNull String settingIdentifier, 404 @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException { 405 if (!PER_PACKAGE_PROTECTED_SETTINGS.contains(settingIdentifier)) { 406 return false; 407 } 408 return isPackageEcmGuarded(packageName, userId); 409 } 410 isAllowlistedPackage(String packageName)411 private boolean isAllowlistedPackage(String packageName) { 412 return isPackageSignedWithAnyOf(packageName, 413 mTrustedPackageCertDigests.get(packageName)); 414 } 415 isAllowlistedInstaller(String packageName)416 private boolean isAllowlistedInstaller(String packageName) { 417 return isPackageSignedWithAnyOf(packageName, 418 mTrustedInstallerCertDigests.get(packageName)); 419 } 420 isPackageSignedWithAnyOf(String packageName, List<byte[]> certDigests)421 private boolean isPackageSignedWithAnyOf(String packageName, List<byte[]> certDigests) { 422 if (packageName != null && certDigests != null) { 423 for (int i = 0, count = certDigests.size(); i < count; i++) { 424 byte[] trustedCertDigest = certDigests.get(i); 425 if (mPackageManager.hasSigningCertificate(packageName, trustedCertDigest, 426 PackageManager.CERT_INPUT_SHA256)) { 427 return true; 428 } 429 } 430 } 431 return false; 432 } 433 434 /** 435 * @return {@code true} if zero {@code <enhanced-confirmation-trusted-installer>} entries 436 * are defined in {@code frameworks/base/data/etc/enhanced-confirmation.xml}; in this case, 437 * we treat all installers as trusted. 438 */ trustPackagesInstalledViaNonAllowlistedInstallers()439 private boolean trustPackagesInstalledViaNonAllowlistedInstallers() { 440 return mTrustedInstallerCertDigests.isEmpty(); 441 } 442 isPackagePreinstalled(@ullable ApplicationInfo applicationInfo)443 private boolean isPackagePreinstalled(@Nullable ApplicationInfo applicationInfo) { 444 if (applicationInfo == null) { 445 return false; 446 } 447 return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 448 } 449 450 @SuppressLint("WrongConstant") setAppEcmState(@onNull String packageName, @EcmState int ecmState, @UserIdInt int userId)451 private void setAppEcmState(@NonNull String packageName, @EcmState int ecmState, 452 @UserIdInt int userId) throws NameNotFoundException { 453 int packageUid = getPackageUid(mPackageManager, packageName, userId); 454 final long identityToken = Binder.clearCallingIdentity(); 455 try { 456 mAppOpsManager.setMode(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, packageUid, 457 packageName, ecmState); 458 } finally { 459 Binder.restoreCallingIdentity(identityToken); 460 } 461 } 462 getAppEcmState(@onNull String packageName, @UserIdInt int userId)463 private @EcmState int getAppEcmState(@NonNull String packageName, @UserIdInt int userId) 464 throws NameNotFoundException { 465 int packageUid = getPackageUid(mPackageManager, packageName, userId); 466 final long identityToken = Binder.clearCallingIdentity(); 467 try { 468 return mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, 469 packageUid, packageName, mAttributionTag, /* message */ null); 470 } finally { 471 Binder.restoreCallingIdentity(identityToken); 472 } 473 } 474 isSettingEcmProtected(@onNull String settingIdentifier)475 private boolean isSettingEcmProtected(@NonNull String settingIdentifier) { 476 if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) 477 || mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 478 return false; 479 } 480 481 if (PER_PACKAGE_PROTECTED_SETTINGS.contains(settingIdentifier)) { 482 return true; 483 } 484 if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) { 485 return true; 486 } 487 // TODO(b/310218979): Add role selections as protected settings 488 return false; 489 } 490 491 // Generate a global protection reason for why the setting may be blocked. Note, this 492 // method will result in a metric being logged, representing a blocked/allowed setting getGlobalProtectionReason(@onNull String settingIdentifier, @NonNull String packageName, @UserIdInt int userId)493 private String getGlobalProtectionReason(@NonNull String settingIdentifier, 494 @NonNull String packageName, @UserIdInt int userId) { 495 if (!UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) { 496 return null; 497 } 498 if (mCallTracker == null) { 499 return null; 500 } 501 String reason = null; 502 if (mCallTracker.isUntrustedCallOngoing()) { 503 if (!AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE.equals(settingIdentifier)) { 504 reason = REASON_PHONE_STATE; 505 } 506 if (!isAccessibilityTool(packageName, userId)) { 507 reason = REASON_PHONE_STATE; 508 } 509 } 510 mCallTracker.onEcmInteraction(packageName, userId, settingIdentifier, reason == null); 511 512 return reason; 513 } 514 isAccessibilityTool(@onNull String packageName, @UserIdInt int userId)515 private boolean isAccessibilityTool(@NonNull String packageName, @UserIdInt int userId) { 516 AccessibilityManager am; 517 synchronized (mUserAccessibilityManagers) { 518 if (!mUserAccessibilityManagers.containsKey(userId)) { 519 Context userContext = 520 getContext().createContextAsUser(UserHandle.of(userId), 0); 521 mUserAccessibilityManagers.put(userId, userContext.getSystemService( 522 AccessibilityManager.class)); 523 } 524 am = mUserAccessibilityManagers.get(userId); 525 } 526 List<AccessibilityServiceInfo> infos = am.getInstalledAccessibilityServiceList(); 527 for (int i = 0; i < infos.size(); i++) { 528 AccessibilityServiceInfo info = infos.get(i); 529 String servicePackageName = null; 530 if (info.getResolveInfo() != null && info.getResolveInfo().serviceInfo != null) { 531 servicePackageName = info.getResolveInfo().serviceInfo.packageName; 532 } 533 if (packageName.equals(servicePackageName)) { 534 return info.isAccessibilityTool(); 535 } 536 } 537 return false; 538 } 539 540 @Nullable getApplicationInfoAsUser(@ullable String packageName, @UserIdInt int userId)541 private ApplicationInfo getApplicationInfoAsUser(@Nullable String packageName, 542 @UserIdInt int userId) { 543 if (packageName == null) { 544 Log.w(LOG_TAG, "The packageName should not be null."); 545 return null; 546 } 547 try { 548 return mPackageManager.getApplicationInfoAsUser(packageName, /* flags */ 0, 549 UserHandle.of(userId)); 550 } catch (NameNotFoundException e) { 551 Log.w(LOG_TAG, "Package not found: " + packageName, e); 552 return null; 553 } 554 } 555 } 556 557 private static class CallTracker { 558 // The time we will remember an untrusted call 559 private static final long UNTRUSTED_CALL_STORAGE_TIME_MS = TimeUnit.HOURS.toMillis(1); 560 // The minimum time that must pass between individual logs of the same call, uid, trusted 561 // status, and allowed setting. 562 private static final long MAX_LOGGING_FREQUENCY_MS = TimeUnit.SECONDS.toMillis(30); 563 // A map of call ID to ongoing or recently removed calls. Concurrent because 564 // additions/removals happen on background threads, but queries on main thread. 565 private final Map<String, TrackedCall> mCalls = new ConcurrentHashMap<>(); 566 567 // A cache of hashed callers, uids, trusted status, and allowed status. Ensures that we 568 // do not log the same interaction too many times 569 private final Map<Integer, Long> mLogCache = new ConcurrentHashMap<>(); 570 571 private class TrackedCall { 572 public @CallType Integer callType; 573 public String caller; 574 575 public long startTime = SystemClock.elapsedRealtime(); 576 577 public long endTime = -1; 578 579 public boolean incoming; 580 581 public boolean blockedDuringCall = false; 582 583 public boolean ecmInteractionDuringCall = false; 584 isFinished()585 public boolean isFinished() { 586 return endTime > 0; 587 } 588 TrackedCall(@onNull Call call)589 TrackedCall(@NonNull Call call) { 590 caller = getPhoneNumber(call); 591 if (caller == null) { 592 caller = getDisplayName(call); 593 } 594 callType = getCallType(call); 595 incoming = call.getDetails().getCallDirection() == Call.Details.DIRECTION_INCOMING; 596 } 597 } 598 599 private Context mContext; 600 private TelephonyManager mTelephonyManager; 601 private PackageManager mPackageManager; 602 CallTracker(Context context)603 CallTracker(Context context) { 604 mContext = context; 605 mTelephonyManager = context.getSystemService(TelephonyManager.class); 606 mPackageManager = context.getPackageManager(); 607 } 608 addCall(@onNull Call call)609 public void addCall(@NonNull Call call) { 610 if (call.getDetails() == null) { 611 return; 612 } 613 pruneOldFinishedCalls(); 614 pruneOldLogs(); 615 mCalls.put(call.getDetails().getId(), new TrackedCall(call)); 616 } 617 endCall(@onNull String callId)618 public void endCall(@NonNull String callId) { 619 TrackedCall trackedCall = mCalls.get(callId); 620 if (trackedCall == null) { 621 // TODO b/379941144: Capture a bug report whenever this happens. 622 return; 623 } 624 if (trackedCall.isFinished()) { 625 return; 626 } 627 if (!Flags.unknownCallSettingBlockedLoggingEnabled()) { 628 mCalls.remove(callId); 629 return; 630 } 631 632 trackedCall.endTime = SystemClock.elapsedRealtime(); 633 if (trackedCall.callType != CALL_TYPE_UNTRUSTED) { 634 // We only hang onto a finished call if the call was untrusted 635 mCalls.remove(callId); 636 } 637 638 if (trackedCall.ecmInteractionDuringCall) { 639 long duration = TimeUnit.MILLISECONDS.toSeconds( 640 trackedCall.endTime - trackedCall.startTime); 641 int durationInt = (int) Math.min(duration, Integer.MAX_VALUE); 642 PermissionControllerStatsLog.write(CALL_WITH_ECM_INTERACTION_REPORTED, 643 trackedCall.blockedDuringCall, durationInt); 644 } 645 646 pruneOldFinishedCalls(); 647 pruneOldLogs(); 648 } 649 endAllCalls()650 public void endAllCalls() { 651 for (String callId: mCalls.keySet()) { 652 endCall(callId); 653 } 654 } 655 onEcmInteraction(@onNull String packageName, int userId, @NonNull String settingIdentifier, boolean allowed)656 public void onEcmInteraction(@NonNull String packageName, int userId, 657 @NonNull String settingIdentifier, boolean allowed) { 658 if (!Flags.unknownCallSettingBlockedLoggingEnabled()) { 659 return; 660 } 661 662 boolean hasOngoingCall = false; 663 for (TrackedCall current: mCalls.values()) { 664 if (current.isFinished()) { 665 // We only care about ongoing calls 666 continue; 667 } 668 hasOngoingCall = true; 669 // Mark that the current call had a setting interaction during it 670 current.ecmInteractionDuringCall = true; 671 current.blockedDuringCall = !allowed; 672 logInCallRestrictionEvent(packageName, userId, settingIdentifier, allowed, current); 673 } 674 if (!hasOngoingCall) { 675 logInCallRestrictionEvent(packageName, userId, settingIdentifier, allowed, null); 676 } 677 678 } 679 getCallType(@onNull Call call)680 private @CallType int getCallType(@NonNull Call call) { 681 String number = getPhoneNumber(call); 682 try { 683 if (number != null && mTelephonyManager.isEmergencyNumber(number)) { 684 return CALL_TYPE_EMERGENCY; 685 } 686 } catch (RuntimeException e) { 687 // If either of these are thrown, the telephony service is not available on the 688 // current device, either because the device lacks telephony calling, or the 689 // telephony service is unavailable. 690 } 691 UserHandle user = mContext.getUser(); 692 /* 693 TODO b/408470449: reenable once call extras are fixed 694 Bundle extras = call.getDetails().getExtras(); 695 if (extras != null) { 696 user = extras.getParcelable(Intent.EXTRA_USER_HANDLE, UserHandle.class); 697 } 698 */ 699 if (number != null) { 700 return hasContactWithPhoneNumber(number, user) 701 ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED; 702 } else { 703 return hasContactWithDisplayName(getDisplayName(call), user) 704 ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED; 705 } 706 } 707 getPhoneNumber(@onNull Call call)708 private static String getPhoneNumber(@NonNull Call call) { 709 Uri handle = call.getDetails().getHandle(); 710 if (handle == null || handle.getScheme() == null) { 711 return null; 712 } 713 if (!handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) { 714 return null; 715 } 716 return handle.getSchemeSpecificPart(); 717 } 718 getDisplayName(@onNull Call call)719 private static String getDisplayName(@NonNull Call call) { 720 return call.getDetails().getCallerDisplayName(); 721 } 722 hasContactWithPhoneNumber(@ullable String phoneNumber, UserHandle user)723 private boolean hasContactWithPhoneNumber(@Nullable String phoneNumber, UserHandle user) { 724 if (phoneNumber == null) { 725 return false; 726 } 727 Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, 728 Uri.encode(phoneNumber)); 729 String[] projection = new String[]{ 730 PhoneLookup.DISPLAY_NAME, 731 ContactsContract.PhoneLookup._ID 732 }; 733 try (Cursor res = getUserContentResolver(user).query(uri, projection, null, null)) { 734 return res != null && res.getCount() > 0; 735 } 736 } 737 hasContactWithDisplayName(@ullable String displayName, UserHandle user)738 private boolean hasContactWithDisplayName(@Nullable String displayName, UserHandle user) { 739 if (displayName == null) { 740 return false; 741 } 742 Uri uri = ContactsContract.Data.CONTENT_URI; 743 String[] projection = new String[]{PhoneLookup._ID}; 744 String selection = StructuredName.DISPLAY_NAME + " = ?"; 745 String[] selectionArgs = new String[]{displayName}; 746 try (Cursor res = getUserContentResolver(user) 747 .query(uri, projection, selection, selectionArgs, null)) { 748 return res != null && res.getCount() > 0; 749 } 750 } 751 getUserContentResolver(UserHandle user)752 private ContentResolver getUserContentResolver(UserHandle user) { 753 return mContext.createContextAsUser(user, 0).getContentResolver(); 754 } 755 getOngoingCallOfType(@allType int callType)756 private TrackedCall getOngoingCallOfType(@CallType int callType) { 757 for (TrackedCall call : mCalls.values()) { 758 if (!call.isFinished() && call.callType == callType) { 759 return call; 760 } 761 } 762 return null; 763 } 764 isUntrustedCallOngoing()765 public boolean isUntrustedCallOngoing() { 766 if (!Flags.unknownCallPackageInstallBlockingEnabled()) { 767 return false; 768 } 769 770 if (getOngoingCallOfType(CALL_TYPE_EMERGENCY) != null) { 771 // If we have an emergency call, return false always. 772 return false; 773 } 774 return getOngoingCallOfType(CALL_TYPE_UNTRUSTED) != null; 775 } 776 pruneOldFinishedCalls()777 void pruneOldFinishedCalls() { 778 if (!Flags.unknownCallSettingBlockedLoggingEnabled()) { 779 return; 780 } 781 long cutoff = SystemClock.elapsedRealtime() - UNTRUSTED_CALL_STORAGE_TIME_MS; 782 mCalls.entrySet().removeIf( 783 e -> e.getValue().isFinished() && e.getValue().endTime < cutoff); 784 } 785 pruneOldLogs()786 void pruneOldLogs() { 787 if (!Flags.unknownCallSettingBlockedLoggingEnabled()) { 788 return; 789 } 790 long cutoff = SystemClock.elapsedRealtime() - MAX_LOGGING_FREQUENCY_MS; 791 mLogCache.entrySet().removeIf(e -> e.getValue() < cutoff); 792 } 793 logInCallRestrictionEvent(@onNull String packageName, int userId, @NonNull String settingIdentifier, boolean allowed, @Nullable TrackedCall call)794 private void logInCallRestrictionEvent(@NonNull String packageName, int userId, 795 @NonNull String settingIdentifier, boolean allowed, @Nullable TrackedCall call) { 796 if (!Flags.unknownCallSettingBlockedLoggingEnabled()) { 797 return; 798 } 799 800 if (!UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) { 801 return; 802 } 803 804 int uid; 805 try { 806 uid = mPackageManager.getPackageUid(packageName, userId); 807 } catch (NameNotFoundException e) { 808 return; 809 } 810 811 boolean callInProgress = call != null && !call.isFinished(); 812 boolean trusted = true; 813 boolean incoming = false; 814 boolean callBackAfterBlock = false; 815 if (callInProgress) { 816 trusted = call.callType != CALL_TYPE_UNTRUSTED; 817 incoming = call.incoming; 818 819 // Look for a previous call from the same caller, that had a blocked ecm interaction 820 for (TrackedCall otherCall : mCalls.values()) { 821 if (!otherCall.isFinished()) { 822 continue; 823 } 824 if (!Objects.equals(otherCall.caller, call.caller)) { 825 continue; 826 } 827 if (!otherCall.blockedDuringCall) { 828 continue; 829 } 830 callBackAfterBlock = true; 831 } 832 } 833 834 String caller = callInProgress ? call.caller : null; 835 int logHash = Objects.hash(caller, uid, settingIdentifier, allowed, trusted); 836 Long lastLogTime = mLogCache.get(logHash); 837 long now = SystemClock.elapsedRealtime(); 838 long cutoff = now - MAX_LOGGING_FREQUENCY_MS; 839 if (lastLogTime != null && lastLogTime > cutoff) { 840 return; 841 } 842 mLogCache.put(logHash, now); 843 844 PermissionControllerStatsLog.write(ECM_RESTRICTION_QUERY_IN_CALL_REPORTED, uid, 845 settingIdentifier, allowed, callInProgress, incoming, trusted, 846 callBackAfterBlock); 847 } 848 } 849 } 850