summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Nate Myren <ntmyren@google.com> 2025-02-20 14:12:44 -0800
committer Nate Myren <ntmyren@google.com> 2025-03-05 09:15:11 -0800
commit8595dc01e81d1d2c4a3357c58e27710330aa994f (patch)
tree8bc3b7c53eb1ca4da8489d632e1102c88de805c6
parent1e6bca7ffcac0f3401a1788e833139844984e258 (diff)
Add logging to ECM in call checks
These will help guage the success of the feature. Also refactors the call tracking to its own class. Bug: 364535720 Test: manual Flag: android.permission.flags.unknown_call_setting_blocked_logging_enabled Relnote: none LOW_COVERAGE_REASON=FLAG_NOT_ENABLED Change-Id: I5b23564b7cb75d8ddeaaed38307d2bb2c19bab0a
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationService.java523
1 files changed, 350 insertions, 173 deletions
diff --git a/service/java/com/android/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java
index 2e83a802b..0566a8285 100644
--- a/service/java/com/android/ecm/EnhancedConfirmationService.java
+++ b/service/java/com/android/ecm/EnhancedConfirmationService.java
@@ -19,6 +19,9 @@ package com.android.ecm;
import static android.app.ecm.EnhancedConfirmationManager.REASON_PACKAGE_RESTRICTED;
import static android.app.ecm.EnhancedConfirmationManager.REASON_PHONE_STATE;
+import static com.android.permission.PermissionStatsLog.CALL_WITH_ECM_INTERACTION_REPORTED;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.ECM_RESTRICTION_QUERY_IN_CALL_REPORTED;
+
import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.FlaggedApi;
@@ -47,6 +50,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
+import android.os.SystemClock;
import android.os.SystemConfigManager;
import android.os.UserHandle;
import android.permission.flags.Flags;
@@ -67,6 +71,7 @@ import androidx.annotation.RequiresApi;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.permission.util.UserUtils;
+import com.android.permissioncontroller.PermissionControllerStatsLog;
import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
@@ -75,8 +80,10 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
/**
@@ -93,12 +100,58 @@ import java.util.concurrent.ConcurrentHashMap;
public class EnhancedConfirmationService extends SystemService {
private static final String LOG_TAG = EnhancedConfirmationService.class.getSimpleName();
+ /** A map of ECM states to their corresponding app op states */
+ @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ECM_STATE_"}, value = {EcmState.ECM_STATE_NOT_GUARDED,
+ EcmState.ECM_STATE_GUARDED, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED,
+ EcmState.ECM_STATE_IMPLICIT})
+ private @interface EcmState {
+ int ECM_STATE_NOT_GUARDED = AppOpsManager.MODE_ALLOWED;
+ int ECM_STATE_GUARDED = AppOpsManager.MODE_ERRORED;
+ int ECM_STATE_GUARDED_AND_ACKNOWLEDGED = AppOpsManager.MODE_IGNORED;
+ int ECM_STATE_IMPLICIT = AppOpsManager.MODE_DEFAULT;
+ }
+
+ private static final ArraySet<String> PER_PACKAGE_PROTECTED_SETTINGS = new ArraySet<>();
+
+ // Settings restricted when an untrusted call is ongoing. These must also be added to
+ // PROTECTED_SETTINGS
+ private static final ArraySet<String> UNTRUSTED_CALL_RESTRICTED_SETTINGS = new ArraySet<>();
+
+ static {
+ // Runtime permissions
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.SEND_SMS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_SMS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.READ_SMS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_MMS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_WAP_PUSH);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.READ_CELL_BROADCASTS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission_group.SMS);
+
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.BIND_DEVICE_ADMIN);
+ // App ops
+ PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_GET_USAGE_STATS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
+ // Default application roles.
+ PER_PACKAGE_PROTECTED_SETTINGS.add(RoleManager.ROLE_DIALER);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(RoleManager.ROLE_SMS);
+
+ if (Flags.unknownCallPackageInstallBlockingEnabled()) {
+ // Requesting package installs, limited during phone calls
+ UNTRUSTED_CALL_RESTRICTED_SETTINGS.add(
+ AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES);
+ UNTRUSTED_CALL_RESTRICTED_SETTINGS.add(
+ AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+ }
+ }
+
private Map<String, List<byte[]>> mTrustedPackageCertDigests;
private Map<String, List<byte[]>> mTrustedInstallerCertDigests;
- // A map of call ID to call type. Thread safe because it is queried on the main thread, but
- // added/removed on a background thread.
- private final Map<String, Integer> mOngoingCalls = new ConcurrentHashMap<>();
+ private static final long UNTRUSTED_CALL_STORAGE_TIME_MS = TimeUnit.HOURS.toMillis(1);
private static final int CALL_TYPE_UNTRUSTED = 0;
private static final int CALL_TYPE_TRUSTED = 1;
private static final int CALL_TYPE_EMERGENCY = 1 << 1;
@@ -116,7 +169,10 @@ public class EnhancedConfirmationService extends SystemService {
new EnhancedConfirmationManagerLocalImpl(this));
}
- private TelephonyManager mTelephonyManager;
+ private PackageManager mPackageManager;
+
+ // A helper which tracks the calls received by the system, and information about them.
+ private CallTracker mCallTracker;
@GuardedBy("mUserAccessibilityManagers")
private final Map<Integer, AccessibilityManager> mUserAccessibilityManagers =
@@ -133,7 +189,11 @@ public class EnhancedConfirmationService extends SystemService {
systemConfigManager.getEnhancedConfirmationTrustedInstallers());
publishBinderService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE, new Stub());
- mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
+
+ if (Flags.unknownCallPackageInstallBlockingEnabled()) {
+ mCallTracker = new CallTracker(getContext());
+ }
+ mPackageManager = getContext().getPackageManager();
}
private Map<String, List<byte[]>> toTrustedPackageMap(Set<SignedPackage> signedPackages) {
@@ -148,115 +208,31 @@ public class EnhancedConfirmationService extends SystemService {
void addOngoingCall(Call call) {
assertNotMainThread();
- if (!Flags.unknownCallPackageInstallBlockingEnabled()) {
- return;
+ if (mCallTracker != null) {
+ mCallTracker.addCall(call);
}
- if (call.getDetails() == null) {
- return;
- }
- mOngoingCalls.put(call.getDetails().getId(), getCallType(call));
}
@WorkerThread
void removeOngoingCall(String callId) {
assertNotMainThread();
- if (!Flags.unknownCallPackageInstallBlockingEnabled()) {
- return;
- }
- Integer returned = mOngoingCalls.remove(callId);
- if (returned == null) {
- // TODO b/379941144: Capture a bug report whenever this happens.
+ if (mCallTracker != null) {
+ mCallTracker.endCall(callId);
}
}
@WorkerThread
void clearOngoingCalls() {
assertNotMainThread();
- mOngoingCalls.clear();
- }
- @WorkerThread
- private @CallType int getCallType(Call call) {
- assertNotMainThread();
- String number = getPhoneNumber(call);
- try {
- if (number != null && mTelephonyManager.isEmergencyNumber(number)) {
- return CALL_TYPE_EMERGENCY;
- }
- } catch (IllegalStateException | UnsupportedOperationException e) {
- // If either of these are thrown, the telephony service is not available on the current
- // device, either because the device lacks telephony calling, or the telephony service
- // is unavailable.
- }
- UserHandle user = getContext().getUser();
- Bundle extras = call.getDetails().getExtras();
- if (extras != null) {
- user = extras.getParcelable(Intent.EXTRA_USER_HANDLE, UserHandle.class);
- }
- if (number != null) {
- return hasContactWithPhoneNumber(number, user)
- ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED;
- } else {
- return hasContactWithDisplayName(call.getDetails().getCallerDisplayName(), user)
- ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED;
+ if (mCallTracker != null) {
+ mCallTracker.endAllCalls();
}
}
- private String getPhoneNumber(Call call) {
- Uri handle = call.getDetails().getHandle();
- if (handle == null || handle.getScheme() == null) {
- return null;
- }
- if (!handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
- return null;
- }
- return handle.getSchemeSpecificPart();
- }
-
- @WorkerThread
- private boolean hasContactWithPhoneNumber(String phoneNumber, UserHandle user) {
- assertNotMainThread();
- if (phoneNumber == null) {
- return false;
- }
- Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
- Uri.encode(phoneNumber));
- String[] projection = new String[]{
- PhoneLookup.DISPLAY_NAME,
- ContactsContract.PhoneLookup._ID
- };
- try (Cursor res = getUserContentResolver(user).query(uri, projection, null, null)) {
- return res != null && res.getCount() > 0;
- }
- }
-
- @WorkerThread
- private boolean hasContactWithDisplayName(String displayName, UserHandle user) {
- assertNotMainThread();
- if (displayName == null) {
- return false;
- }
- Uri uri = ContactsContract.Data.CONTENT_URI;
- String[] projection = new String[]{PhoneLookup._ID};
- String selection = StructuredName.DISPLAY_NAME + " = ?";
- String[] selectionArgs = new String[]{displayName};
- try (Cursor res = getUserContentResolver(user)
- .query(uri, projection, selection, selectionArgs, null)) {
- return res != null && res.getCount() > 0;
- }
- }
-
- private ContentResolver getUserContentResolver(UserHandle user) {
- return getContext().createContextAsUser(user, 0).getContentResolver();
- }
-
- private boolean hasCallOfType(@CallType int callType) {
- for (int ongoingCallType : mOngoingCalls.values()) {
- if (ongoingCallType == callType) {
- return true;
- }
- }
- return false;
+ static int getPackageUid(PackageManager pm, String packageName,
+ int userId) throws NameNotFoundException {
+ return pm.getPackageUidAsUser(packageName, PackageManager.PackageInfoFlags.of(0), userId);
}
private void assertNotMainThread() throws IllegalStateException {
@@ -267,65 +243,15 @@ public class EnhancedConfirmationService extends SystemService {
private class Stub extends IEnhancedConfirmationManager.Stub {
- /** A map of ECM states to their corresponding app op states */
- @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
- @IntDef(prefix = {"ECM_STATE_"}, value = {EcmState.ECM_STATE_NOT_GUARDED,
- EcmState.ECM_STATE_GUARDED, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED,
- EcmState.ECM_STATE_IMPLICIT})
- private @interface EcmState {
- int ECM_STATE_NOT_GUARDED = AppOpsManager.MODE_ALLOWED;
- int ECM_STATE_GUARDED = AppOpsManager.MODE_ERRORED;
- int ECM_STATE_GUARDED_AND_ACKNOWLEDGED = AppOpsManager.MODE_IGNORED;
- int ECM_STATE_IMPLICIT = AppOpsManager.MODE_DEFAULT;
- }
-
- private static final ArraySet<String> PER_PACKAGE_PROTECTED_SETTINGS = new ArraySet<>();
-
- // Settings restricted when an untrusted call is ongoing. These must also be added to
- // PROTECTED_SETTINGS
- private static final ArraySet<String> UNTRUSTED_CALL_RESTRICTED_SETTINGS = new ArraySet<>();
-
- static {
- // Runtime permissions
- PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.SEND_SMS);
- PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_SMS);
- PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.READ_SMS);
- PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_MMS);
- PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_WAP_PUSH);
- PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.READ_CELL_BROADCASTS);
- PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission_group.SMS);
-
- PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.BIND_DEVICE_ADMIN);
- // App ops
- PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
- PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
- PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
- PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_GET_USAGE_STATS);
- PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
- // Default application roles.
- PER_PACKAGE_PROTECTED_SETTINGS.add(RoleManager.ROLE_DIALER);
- PER_PACKAGE_PROTECTED_SETTINGS.add(RoleManager.ROLE_SMS);
-
- if (Flags.unknownCallPackageInstallBlockingEnabled()) {
- // Requesting package installs, limited during phone calls
- UNTRUSTED_CALL_RESTRICTED_SETTINGS.add(
- AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES);
- UNTRUSTED_CALL_RESTRICTED_SETTINGS.add(
- AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
- }
- }
-
private final @NonNull Context mContext;
private final String mAttributionTag;
private final AppOpsManager mAppOpsManager;
- private final PackageManager mPackageManager;
Stub() {
Context context = getContext();
mContext = context;
mAttributionTag = context.getAttributionTag();
mAppOpsManager = context.getSystemService(AppOpsManager.class);
- mPackageManager = context.getPackageManager();
}
public boolean isRestricted(@NonNull String packageName, @NonNull String settingIdentifier,
@@ -380,7 +306,7 @@ public class EnhancedConfirmationService extends SystemService {
}
setAppEcmState(packageName, EcmState.ECM_STATE_NOT_GUARDED, userId);
EnhancedConfirmationStatsLogUtils.INSTANCE.logRestrictionCleared(
- getPackageUid(packageName, userId));
+ getPackageUid(mPackageManager, packageName, userId));
} catch (NameNotFoundException e) {
throw new IllegalArgumentException(e);
}
@@ -421,18 +347,6 @@ public class EnhancedConfirmationService extends SystemService {
}
}
- private boolean isUntrustedCallOngoing() {
- if (!Flags.unknownCallPackageInstallBlockingEnabled()) {
- return false;
- }
-
- if (hasCallOfType(CALL_TYPE_EMERGENCY)) {
- // If we have an emergency call, return false always.
- return false;
- }
- return hasCallOfType(CALL_TYPE_UNTRUSTED);
- }
-
private void enforcePermissions(@NonNull String methodName, @UserIdInt int userId) {
UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
/* enforceForProfileGroup= */ false, methodName, mContext);
@@ -540,7 +454,7 @@ public class EnhancedConfirmationService extends SystemService {
@SuppressLint("WrongConstant")
private void setAppEcmState(@NonNull String packageName, @EcmState int ecmState,
@UserIdInt int userId) throws NameNotFoundException {
- int packageUid = getPackageUid(packageName, userId);
+ int packageUid = getPackageUid(mPackageManager, packageName, userId);
final long identityToken = Binder.clearCallingIdentity();
try {
mAppOpsManager.setMode(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, packageUid,
@@ -552,7 +466,7 @@ public class EnhancedConfirmationService extends SystemService {
private @EcmState int getAppEcmState(@NonNull String packageName, @UserIdInt int userId)
throws NameNotFoundException {
- int packageUid = getPackageUid(packageName, userId);
+ int packageUid = getPackageUid(mPackageManager, packageName, userId);
final long identityToken = Binder.clearCallingIdentity();
try {
return mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS,
@@ -578,19 +492,28 @@ public class EnhancedConfirmationService extends SystemService {
return false;
}
+ // Generate a global protection reason for why the setting may be blocked. Note, this
+ // method will result in a metric being logged, representing a blocked/allowed setting
private String getGlobalProtectionReason(@NonNull String settingIdentifier,
@NonNull String packageName, @UserIdInt int userId) {
- if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)
- && isUntrustedCallOngoing()) {
+ if (!UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) {
+ return null;
+ }
+ if (mCallTracker == null) {
+ return null;
+ }
+ String reason = null;
+ if (mCallTracker.isUntrustedCallOngoing()) {
if (!AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE.equals(settingIdentifier)) {
- return REASON_PHONE_STATE;
+ reason = REASON_PHONE_STATE;
}
if (!isAccessibilityTool(packageName, userId)) {
- return REASON_PHONE_STATE;
+ reason = REASON_PHONE_STATE;
}
- return null;
}
- return null;
+ mCallTracker.onEcmInteraction(packageName, userId, settingIdentifier, reason == null);
+
+ return reason;
}
private boolean isAccessibilityTool(@NonNull String packageName, @UserIdInt int userId) {
@@ -633,11 +556,265 @@ public class EnhancedConfirmationService extends SystemService {
return null;
}
}
+ }
- private int getPackageUid(@NonNull String packageName, @UserIdInt int userId)
- throws NameNotFoundException {
- return mPackageManager.getApplicationInfoAsUser(packageName, /* flags */ 0,
- UserHandle.of(userId)).uid;
+ private static class CallTracker {
+ // A map of call ID to ongoing or recently removed calls. Concurrent because
+ // additions/removals happen on background threads, but queries on main thread.
+ private final Map<String, TrackedCall> mCalls = new ConcurrentHashMap<>();
+
+ private class TrackedCall {
+ public @CallType Integer callType;
+ public String caller;
+
+ public long startTime = SystemClock.elapsedRealtime();
+
+ public long endTime = -1;
+
+ public boolean incoming;
+
+ public boolean blockedDuringCall = false;
+
+ public boolean ecmInteractionDuringCall = false;
+
+ public boolean isFinished() {
+ return endTime > 0;
+ }
+
+ TrackedCall(@NonNull Call call) {
+ caller = getPhoneNumber(call);
+ if (caller == null) {
+ caller = getDisplayName(call);
+ }
+ callType = getCallType(call);
+ incoming = call.getDetails().getCallDirection() == Call.Details.DIRECTION_INCOMING;
+ }
+ }
+
+ private Context mContext;
+ private TelephonyManager mTelephonyManager;
+ private PackageManager mPackageManager;
+
+ CallTracker(Context context) {
+ mContext = context;
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mPackageManager = context.getPackageManager();
+ }
+
+ public void addCall(@NonNull Call call) {
+ if (call.getDetails() == null) {
+ return;
+ }
+ pruneOldFinishedCalls();
+ mCalls.put(call.getDetails().getId(), new TrackedCall(call));
+ }
+
+ public void endCall(@NonNull String callId) {
+ TrackedCall trackedCall = mCalls.get(callId);
+ if (trackedCall == null) {
+ // TODO b/379941144: Capture a bug report whenever this happens.
+ return;
+ }
+ if (trackedCall.isFinished()) {
+ return;
+ }
+ if (!Flags.unknownCallSettingBlockedLoggingEnabled()) {
+ mCalls.remove(callId);
+ return;
+ }
+
+ trackedCall.endTime = SystemClock.elapsedRealtime();
+ if (trackedCall.callType != CALL_TYPE_UNTRUSTED) {
+ // We only hang onto a finished call if the call was untrusted
+ mCalls.remove(callId);
+ }
+
+ if (trackedCall.ecmInteractionDuringCall) {
+ long duration = TimeUnit.MILLISECONDS.toSeconds(
+ trackedCall.endTime - trackedCall.startTime);
+ int durationInt = (int) Math.min(duration, Integer.MAX_VALUE);
+ PermissionControllerStatsLog.write(CALL_WITH_ECM_INTERACTION_REPORTED,
+ trackedCall.blockedDuringCall, durationInt);
+ }
+
+ pruneOldFinishedCalls();
+ }
+
+ public void endAllCalls() {
+ for (String callId: mCalls.keySet()) {
+ endCall(callId);
+ }
+ }
+
+ public void onEcmInteraction(@NonNull String packageName, int userId,
+ @NonNull String settingIdentifier, boolean allowed) {
+ if (!Flags.unknownCallSettingBlockedLoggingEnabled()) {
+ return;
+ }
+
+ boolean hasOngoingCall = false;
+ for (TrackedCall current: mCalls.values()) {
+ if (current.isFinished()) {
+ // We only care about ongoing calls
+ continue;
+ }
+ hasOngoingCall = true;
+ // Mark that the current call had a setting interaction during it
+ current.ecmInteractionDuringCall = true;
+ current.blockedDuringCall = !allowed;
+ logInCallRestrictionEvent(packageName, userId, settingIdentifier, allowed, current);
+ }
+ if (!hasOngoingCall) {
+ logInCallRestrictionEvent(packageName, userId, settingIdentifier, allowed, null);
+ }
+
+ }
+
+ private @CallType int getCallType(@NonNull Call call) {
+ String number = getPhoneNumber(call);
+ try {
+ if (number != null && mTelephonyManager.isEmergencyNumber(number)) {
+ return CALL_TYPE_EMERGENCY;
+ }
+ } catch (IllegalStateException | UnsupportedOperationException e) {
+ // If either of these are thrown, the telephony service is not available on the
+ // current device, either because the device lacks telephony calling, or the
+ // telephony service is unavailable.
+ }
+ UserHandle user = mContext.getUser();
+ Bundle extras = call.getDetails().getExtras();
+ if (extras != null) {
+ user = extras.getParcelable(Intent.EXTRA_USER_HANDLE, UserHandle.class);
+ }
+ if (number != null) {
+ return hasContactWithPhoneNumber(number, user)
+ ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED;
+ } else {
+ return hasContactWithDisplayName(getDisplayName(call), user)
+ ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED;
+ }
+ }
+
+ private static String getPhoneNumber(@NonNull Call call) {
+ Uri handle = call.getDetails().getHandle();
+ if (handle == null || handle.getScheme() == null) {
+ return null;
+ }
+ if (!handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
+ return null;
+ }
+ return handle.getSchemeSpecificPart();
+ }
+
+ private static String getDisplayName(@NonNull Call call) {
+ return call.getDetails().getCallerDisplayName();
+ }
+
+ private boolean hasContactWithPhoneNumber(@Nullable String phoneNumber, UserHandle user) {
+ if (phoneNumber == null) {
+ return false;
+ }
+ Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(phoneNumber));
+ String[] projection = new String[]{
+ PhoneLookup.DISPLAY_NAME,
+ ContactsContract.PhoneLookup._ID
+ };
+ try (Cursor res = getUserContentResolver(user).query(uri, projection, null, null)) {
+ return res != null && res.getCount() > 0;
+ }
+ }
+
+ private boolean hasContactWithDisplayName(@Nullable String displayName, UserHandle user) {
+ if (displayName == null) {
+ return false;
+ }
+ Uri uri = ContactsContract.Data.CONTENT_URI;
+ String[] projection = new String[]{PhoneLookup._ID};
+ String selection = StructuredName.DISPLAY_NAME + " = ?";
+ String[] selectionArgs = new String[]{displayName};
+ try (Cursor res = getUserContentResolver(user)
+ .query(uri, projection, selection, selectionArgs, null)) {
+ return res != null && res.getCount() > 0;
+ }
+ }
+
+ private ContentResolver getUserContentResolver(UserHandle user) {
+ return mContext.createContextAsUser(user, 0).getContentResolver();
+ }
+
+ private TrackedCall getOngoingCallOfType(@CallType int callType) {
+ for (TrackedCall call : mCalls.values()) {
+ if (!call.isFinished() && call.callType == callType) {
+ return call;
+ }
+ }
+ return null;
+ }
+
+ public boolean isUntrustedCallOngoing() {
+ if (!Flags.unknownCallPackageInstallBlockingEnabled()) {
+ return false;
+ }
+
+ if (getOngoingCallOfType(CALL_TYPE_EMERGENCY) != null) {
+ // If we have an emergency call, return false always.
+ return false;
+ }
+ return getOngoingCallOfType(CALL_TYPE_UNTRUSTED) != null;
+ }
+
+ void pruneOldFinishedCalls() {
+ if (!Flags.unknownCallSettingBlockedLoggingEnabled()) {
+ return;
+ }
+ long cutoff = SystemClock.elapsedRealtime() - UNTRUSTED_CALL_STORAGE_TIME_MS;
+ mCalls.entrySet().removeIf(
+ e -> e.getValue().isFinished() && e.getValue().endTime < cutoff);
+ }
+ private void logInCallRestrictionEvent(@NonNull String packageName, int userId,
+ @NonNull String settingIdentifier, boolean allowed, @Nullable TrackedCall call) {
+ if (!Flags.unknownCallSettingBlockedLoggingEnabled()) {
+ return;
+ }
+
+ if (!UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) {
+ return;
+ }
+
+ int uid;
+ try {
+ uid = mPackageManager.getPackageUid(packageName, userId);
+ } catch (NameNotFoundException e) {
+ return;
+ }
+
+ boolean callInProgress = call != null && !call.isFinished();
+ boolean trusted = true;
+ boolean incoming = false;
+ boolean callBackAfterBlock = false;
+ if (callInProgress) {
+ trusted = call.callType != CALL_TYPE_UNTRUSTED;
+ incoming = call.incoming;
+
+ // Look for a previous call from the same caller, that had a blocked ecm interaction
+ for (TrackedCall otherCall : mCalls.values()) {
+ if (!otherCall.isFinished()) {
+ continue;
+ }
+ if (!Objects.equals(otherCall.caller, call.caller)) {
+ continue;
+ }
+ if (!otherCall.blockedDuringCall) {
+ continue;
+ }
+ callBackAfterBlock = true;
+ }
+ }
+
+ PermissionControllerStatsLog.write(ECM_RESTRICTION_QUERY_IN_CALL_REPORTED, uid,
+ settingIdentifier, allowed, callInProgress, incoming, trusted,
+ callBackAfterBlock);
}
}
}