summaryrefslogtreecommitdiff
path: root/service
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 /service
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
Diffstat (limited to 'service')
-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);
}
}
}