summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
Diffstat (limited to 'service')
-rw-r--r--service/api/system-server-current.txt14
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java80
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java56
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java59
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationService.java133
5 files changed, 340 insertions, 2 deletions
diff --git a/service/api/system-server-current.txt b/service/api/system-server-current.txt
index 30fbab484..a3db89370 100644
--- a/service/api/system-server-current.txt
+++ b/service/api/system-server-current.txt
@@ -1,4 +1,18 @@
// Signature format: 2.0
+package com.android.ecm {
+
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") public class EnhancedConfirmationCallTrackerService extends android.telecom.InCallService {
+ ctor public EnhancedConfirmationCallTrackerService();
+ }
+
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") public interface EnhancedConfirmationManagerLocal {
+ method public void addOngoingCall(@NonNull android.telecom.Call);
+ method public void clearOngoingCalls();
+ method public void removeOngoingCall(@NonNull String);
+ }
+
+}
+
package com.android.permission.persistence {
public interface RuntimePermissionsPersistence {
diff --git a/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java b/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java
new file mode 100644
index 000000000..407d56f70
--- /dev/null
+++ b/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ecm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.telecom.Call;
+import android.telecom.InCallService;
+
+import com.android.server.LocalManagerRegistry;
+
+/**
+ * @hide
+ *
+ * This InCallService tracks called (both incoming and outgoing), and sends their information to the
+ * EnhancedConfirmationService
+ *
+ **/
+@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED)
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
+public class EnhancedConfirmationCallTrackerService extends InCallService {
+ private EnhancedConfirmationManagerLocal mEnhancedConfirmationManagerLocal;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (Flags.enhancedConfirmationInCallApisEnabled()) {
+ mEnhancedConfirmationManagerLocal =
+ LocalManagerRegistry.getManager(EnhancedConfirmationManagerLocal.class);
+ }
+ }
+
+ @Override
+ public void onCallAdded(@Nullable Call call) {
+ if (mEnhancedConfirmationManagerLocal == null || call == null) {
+ return;
+ }
+
+ mEnhancedConfirmationManagerLocal.addOngoingCall(call);
+ }
+
+ @Override
+ public void onCallRemoved(@Nullable Call call) {
+ if (mEnhancedConfirmationManagerLocal == null || call == null) {
+ return;
+ }
+
+ mEnhancedConfirmationManagerLocal.removeOngoingCall(call.getDetails().getId());
+ }
+
+ /**
+ * When unbound, we should assume all calls have finished. Notify the system of such.
+ */
+ public boolean onUnbind(@Nullable Intent intent) {
+ if (mEnhancedConfirmationManagerLocal != null) {
+ mEnhancedConfirmationManagerLocal.clearOngoingCalls();
+ }
+ return super.onUnbind(intent);
+ }
+}
diff --git a/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java b/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java
new file mode 100644
index 000000000..483071716
--- /dev/null
+++ b/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ecm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.telecom.Call;
+
+/**
+ * @hide
+ *
+ * In-process API for the Enhanced Confirmation Service
+ */
+@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED)
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
+public interface EnhancedConfirmationManagerLocal {
+ /**
+ * Inform the enhanced confirmation service of an ongoing call
+ *
+ * @param call The call to potentially track
+ *
+ */
+ void addOngoingCall(@NonNull Call call);
+
+ /**
+ * Inform the enhanced confirmation service that a call has ended
+ *
+ * @param callId The ID of the call to stop tracking
+ *
+ */
+ void removeOngoingCall(@NonNull String callId);
+
+ /**
+ * Informs the enhanced confirmation service it should clear out any ongoing calls
+ */
+ void clearOngoingCalls();
+}
diff --git a/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java b/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java
new file mode 100644
index 000000000..a5c6d3c36
--- /dev/null
+++ b/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ecm;
+
+import android.annotation.NonNull;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.telecom.Call;
+
+/** @hide */
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
+class EnhancedConfirmationManagerLocalImpl implements EnhancedConfirmationManagerLocal {
+
+ private final EnhancedConfirmationService mService;
+
+ EnhancedConfirmationManagerLocalImpl(EnhancedConfirmationService service) {
+ if (Flags.enhancedConfirmationInCallApisEnabled()) {
+ mService = service;
+ } else {
+ mService = null;
+ }
+ }
+
+ @Override
+ public void addOngoingCall(@NonNull Call call) {
+ if (mService != null) {
+ mService.addOngoingCall(call);
+ }
+ }
+
+ @Override
+ public void removeOngoingCall(@NonNull String callId) {
+ if (mService != null) {
+ mService.removeOngoingCall(callId);
+ }
+ }
+
+ @Override
+ public void clearOngoingCalls() {
+ if (mService != null) {
+ mService.clearOngoingCalls();
+ }
+ }
+}
diff --git a/service/java/com/android/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java
index 708884e85..c96044db2 100644
--- a/service/java/com/android/ecm/EnhancedConfirmationService.java
+++ b/service/java/com/android/ecm/EnhancedConfirmationService.java
@@ -19,11 +19,15 @@ package com.android.ecm;
import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.ecm.EnhancedConfirmationManager;
import android.app.ecm.IEnhancedConfirmationManager;
import android.app.role.RoleManager;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
@@ -31,25 +35,33 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.SignedPackage;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.SystemConfigManager;
import android.os.UserHandle;
import android.permission.flags.Flags;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.PhoneLookup;
+import android.telecom.Call;
+import android.telecom.PhoneAccount;
+import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.Keep;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.internal.util.Preconditions;
import com.android.permission.util.UserUtils;
+import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -66,16 +78,35 @@ import java.util.Set;
@Keep
@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SuppressLint("MissingPermission")
public class EnhancedConfirmationService extends SystemService {
private static final String LOG_TAG = EnhancedConfirmationService.class.getSimpleName();
private Map<String, List<byte[]>> mTrustedPackageCertDigests;
private Map<String, List<byte[]>> mTrustedInstallerCertDigests;
+ // A map of call ID to call type
+ private final Map<String, Integer> mOngoingCalls = new ArrayMap<>();
+
+ private static final int CALL_TYPE_UNTRUSTED = 0;
+ private static final int CALL_TYPE_TRUSTED = 1;
+ private static final int CALL_TYPE_EMERGENCY = 2;
+ @IntDef(flag = true, value = {
+ CALL_TYPE_UNTRUSTED,
+ CALL_TYPE_TRUSTED,
+ CALL_TYPE_EMERGENCY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CallType {}
public EnhancedConfirmationService(@NonNull Context context) {
super(context);
+ LocalManagerRegistry.addManager(EnhancedConfirmationManagerLocal.class,
+ new EnhancedConfirmationManagerLocalImpl(this));
}
+ private ContentResolver mContentResolver;
+ private TelephonyManager mTelephonyManager;
+
@Override
public void onStart() {
Context context = getContext();
@@ -87,6 +118,8 @@ public class EnhancedConfirmationService extends SystemService {
systemConfigManager.getEnhancedConfirmationTrustedInstallers());
publishBinderService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE, new Stub());
+ mContentResolver = getContext().getContentResolver();
+ mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
}
private Map<String, List<byte[]>> toTrustedPackageMap(Set<SignedPackage> signedPackages) {
@@ -99,6 +132,90 @@ public class EnhancedConfirmationService extends SystemService {
return trustedPackageMap;
}
+ void addOngoingCall(Call call) {
+ if (!Flags.enhancedConfirmationInCallApisEnabled()) {
+ return;
+ }
+ if (call.getDetails() == null) {
+ return;
+ }
+ mOngoingCalls.put(call.getDetails().getId(), getCallType(call));
+ }
+
+ void removeOngoingCall(String callId) {
+ if (!Flags.enhancedConfirmationInCallApisEnabled()) {
+ return;
+ }
+ Integer returned = mOngoingCalls.remove(callId);
+ if (returned == null) {
+ // TODO b/379941144: Capture a bug report whenever this happens.
+ }
+ }
+
+ void clearOngoingCalls() {
+ mOngoingCalls.clear();
+ }
+
+ private @CallType int getCallType(Call call) {
+ String number = getPhoneNumber(call);
+ if (number != null && mTelephonyManager.isEmergencyNumber(number)) {
+ return CALL_TYPE_EMERGENCY;
+ } else if (number != null) {
+ return hasContactWithPhoneNumber(number) ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED;
+ } else {
+ return hasContactWithDisplayName(call.getDetails().getCallerDisplayName())
+ ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED;
+ }
+ }
+
+ 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();
+ }
+
+ private boolean hasContactWithPhoneNumber(String phoneNumber) {
+ 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 = mContentResolver.query(uri, projection, null, null)) {
+ return res != null && res.getCount() > 0;
+ }
+ }
+
+ private boolean hasContactWithDisplayName(String displayName) {
+ 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 = mContentResolver.query(uri, projection, selection, selectionArgs, null)) {
+ return res != null && res.getCount() > 0;
+ }
+ }
+
+ private boolean hasCallOfType(@CallType int callType) {
+ for (int ongoingCallType : mOngoingCalls.values()) {
+ if (ongoingCallType == callType) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private class Stub extends IEnhancedConfirmationManager.Stub {
/** A map of ECM states to their corresponding app op states */
@@ -227,6 +344,17 @@ public class EnhancedConfirmationService extends SystemService {
}
}
+ @Override
+ public boolean isUntrustedCallOngoing() {
+ enforcePermissions("isUntrustedCallOngoing",
+ UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier());
+ 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, false, methodName, mContext);
mContext.enforceCallingOrSelfPermission(
@@ -322,6 +450,7 @@ public class EnhancedConfirmationService extends SystemService {
return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
+ @SuppressLint("WrongConstant")
private void setAppEcmState(@NonNull String packageName, @EcmState int ecmState,
@UserIdInt int userId) throws NameNotFoundException {
int packageUid = getPackageUid(packageName, userId);