Loading src/com/android/server/telecom/CallScreeningServiceHelper.java 0 → 100644 +247 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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.server.telecom; import android.Manifest; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.telecom.CallIdentification; import android.telecom.CallScreeningService; import android.telecom.Log; import android.telecom.Logging.Session; import android.text.TextUtils; import com.android.internal.telecom.ICallScreeningAdapter; import com.android.internal.telecom.ICallScreeningService; import java.util.List; import java.util.concurrent.CompletableFuture; /** * Helper class for performing operations with {@link CallScreeningService}s. */ public class CallScreeningServiceHelper { private static final String TAG = CallScreeningServiceHelper.class.getSimpleName(); /** * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an * app. */ public interface AppLabelProxy { String getAppLabel(String packageName); } /** * Implementation of {@link CallScreeningService} adapter AIDL; provides a means for responses * from the call screening service to be handled. */ private class CallScreeningAdapter extends ICallScreeningAdapter.Stub { @Override public void allowCall(String s) throws RemoteException { // no-op; we don't allow this on outgoing calls. } @Override public void disallowCall(String s, boolean b, boolean b1, boolean b2, ComponentName componentName) throws RemoteException { // no-op; we don't allow this on outgoing calls. } @Override public void provideCallIdentification(String callId, CallIdentification callIdentification) throws RemoteException { Log.startSession("CSA.pCI"); long token = Binder.clearCallingIdentity(); try { synchronized (mTelecomLock) { if (mCall != null && mCall.getId().equals(callId)) { Log.i(TAG, "provideCallIdentification - got call ID"); callIdentification.setCallScreeningAppName(mAppLabelProxy.getAppLabel( mPackageName)); callIdentification.setCallScreeningPackageName(mPackageName); mFuture.complete(callIdentification); } } } finally { Binder.restoreCallingIdentity(token); Log.endSession(); } mFuture.complete(null); } } private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter; private final TelecomSystem.SyncRoot mTelecomLock; private final Call mCall; private final UserHandle mUserHandle; private final Context mContext; private final AppLabelProxy mAppLabelProxy; private final Session mLoggingSession; private CompletableFuture<CallIdentification> mFuture; private String mPackageName; public CallScreeningServiceHelper(Context context, TelecomSystem.SyncRoot telecomLock, String packageName, ParcelableCallUtils.Converter converter, UserHandle userHandle, Call call, AppLabelProxy appLabelProxy) { mContext = context; mTelecomLock = telecomLock; mParcelableCallUtilsConverter = converter; mCall = call; mUserHandle = userHandle; mPackageName = packageName; mAppLabelProxy = appLabelProxy; mLoggingSession = Log.createSubsession(); } /** * Builds a {@link CompletableFuture} which performs a bind to a {@link CallScreeningService} * @return */ public CompletableFuture<CallIdentification> process() { Log.d(this, "process"); return bindAndGetCallIdentification(); } public CompletableFuture<CallIdentification> bindAndGetCallIdentification() { Log.d(this, "bindAndGetCallIdentification"); if (mPackageName == null) { return CompletableFuture.completedFuture(null); } mFuture = new CompletableFuture<>(); ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { ICallScreeningService screeningService = ICallScreeningService.Stub.asInterface(service); Log.continueSession(mLoggingSession, "CSSH.oSC"); try { try { screeningService.screenCall(new CallScreeningAdapter(), mParcelableCallUtilsConverter.toParcelableCallForScreening(mCall)); } catch (RemoteException e) { Log.w(CallScreeningServiceHelper.this, "Cancelling call id due to remote exception"); mFuture.complete(null); } } finally { Log.endSession(); } } @Override public void onServiceDisconnected(ComponentName name) { // No locking needed -- CompletableFuture only lets one thread call complete. Log.continueSession(mLoggingSession, "CSSH.oSD"); try { if (!mFuture.isDone()) { Log.w(CallScreeningServiceHelper.this, "Cancelling outgoing call screen due to service disconnect."); } mFuture.complete(null); } finally { Log.endSession(); } } }; if (!bindCallScreeningService(mContext, mUserHandle, mPackageName, serviceConnection)) { Log.i(this, "bindAndGetCallIdentification - bind failed"); Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName); mFuture.complete(null); } // Set up a timeout so that we're not waiting forever for the caller ID information. Handler handler = new Handler(); handler.postDelayed(() -> { // No locking needed -- CompletableFuture only lets one thread call complete. Log.continueSession(mLoggingSession, "CSSH.timeout"); try { if (!mFuture.isDone()) { Log.w(TAG, "Cancelling call id process due to timeout"); } mFuture.complete(null); } finally { Log.endSession(); } }, Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver())); return mFuture; } /** * Binds to a {@link CallScreeningService}. * @param context The current context. * @param userHandle User to bind as. * @param packageName Package name of the {@link CallScreeningService}. * @param serviceConnection The {@link ServiceConnection} to be notified of binding. * @return {@code true} if binding succeeds, {@code false} otherwise. */ public static boolean bindCallScreeningService(Context context, UserHandle userHandle, String packageName, ServiceConnection serviceConnection) { if (TextUtils.isEmpty(packageName)) { Log.i(TAG, "PackageName is empty. Not performing call screening."); return false; } Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE) .setPackage(packageName); List<ResolveInfo> entries = context.getPackageManager().queryIntentServicesAsUser( intent, 0, userHandle.getIdentifier()); if (entries.isEmpty()) { Log.i(TAG, packageName + " has no call screening service defined."); return false; } ResolveInfo entry = entries.get(0); if (entry.serviceInfo == null) { Log.w(TAG, packageName + " call screening service has invalid service info"); return false; } if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals( Manifest.permission.BIND_SCREENING_SERVICE)) { Log.w(TAG, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " + entry.serviceInfo.packageName); return false; } ComponentName componentName = new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name); intent.setComponent(componentName); if (context.bindServiceAsUser( intent, serviceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, UserHandle.CURRENT)) { Log.d(TAG, "bindService, found service, waiting for it to connect"); return true; } return false; } } src/com/android/server/telecom/CallerInfoLookupHelper.java +43 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.telecom.Log; import android.telecom.Logging.Runnable; import android.telecom.Logging.Session; import android.text.TextUtils; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CallerInfo; Loading @@ -37,6 +38,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; public class CallerInfoLookupHelper { public interface OnQueryCompleteListener { Loading Loading @@ -77,6 +79,47 @@ public class CallerInfoLookupHelper { mLock = lock; } /** * Generates a CompletableFuture which performs a contacts lookup asynchronously. The future * returns a {@link Pair} containing the original handle which is being looked up and any * {@link CallerInfo} which was found. * @param handle * @return {@link CompletableFuture} to perform the contacts lookup. */ public CompletableFuture<Pair<Uri, CallerInfo>> startLookup(final Uri handle) { // Create the returned future and final CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>(); final String number = handle.getSchemeSpecificPart(); if (TextUtils.isEmpty(number)) { // Nothing to do here, just finish. Log.d(CallerInfoLookupHelper.this, "onCallerInfoQueryComplete - no number; end early"); callerInfoFuture.complete(new Pair<>(handle, null)); return callerInfoFuture; } // Setup a query complete listener which will get the results of the contacts lookup. OnQueryCompleteListener listener = new OnQueryCompleteListener() { @Override public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) { Log.d(CallerInfoLookupHelper.this, "onCallerInfoQueryComplete - found info for %s", Log.piiHandle(handle)); // Got something, so complete the future. callerInfoFuture.complete(new Pair<>(handle, info)); } @Override public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) { // No-op for now; not something this future cares about. } }; // Start async lookup. startLookup(handle, listener); return callerInfoFuture; } public void startLookup(final Uri handle, OnQueryCompleteListener listener) { if (handle == null) { listener.onCallerInfoQueryComplete(handle, null); Loading src/com/android/server/telecom/CallsManager.java +74 −1 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import android.provider.BlockedNumberContract.SystemContract; import android.provider.CallLog.Calls; import android.provider.Settings; import android.telecom.CallAudioState; import android.telecom.CallIdentification; import android.telecom.Conference; import android.telecom.Connection; import android.telecom.DisconnectCause; Loading Loading @@ -614,7 +615,7 @@ public class CallsManager extends Call.ListenerBase filters.add(new CallScreeningServiceController(mContext, this, mPhoneAccountRegistrar, new ParcelableCallUtils.Converter(), mLock, new TelecomServiceImpl.SettingsSecureAdapterImpl(), mCallerInfoLookupHelper, new CallScreeningServiceController.AppLabelProxy() { new CallScreeningServiceHelper.AppLabelProxy() { @Override public String getAppLabel(String packageName) { PackageManager pm = mContext.getPackageManager(); Loading Loading @@ -1447,6 +1448,34 @@ public class CallsManager extends Call.ListenerBase return mPendingAccountSelection; }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSPA")); // Potentially perform call identification for dialed TEL scheme numbers. if (PhoneAccount.SCHEME_TEL.equals(handle.getScheme())) { // Perform an asynchronous contacts lookup in this stage; ensure post-dial digits are // not included. CompletableFuture<Pair<Uri, CallerInfo>> contactLookupFuture = mCallerInfoLookupHelper.startLookup(Uri.fromParts(handle.getScheme(), PhoneNumberUtils.extractNetworkPortion(handle.getSchemeSpecificPart()), null)); // Once the phone account selection stage has completed, we can handle the results from // that with the contacts lookup in order to determine if we should lookup bind to the // CallScreeningService in order for it to potentially provide caller ID. dialerSelectPhoneAccountFuture.thenAcceptBothAsync(contactLookupFuture, (callPhoneAccountHandlePair, uriCallerInfoPair) -> { Call theCall = callPhoneAccountHandlePair.first; boolean isInContacts = uriCallerInfoPair.second != null && uriCallerInfoPair.second.contactExists; Log.d(CallsManager.this, "outgoingCallIdStage: isInContacts=%s", isInContacts); // We only want to provide a CallScreeningService with a call if its not in // contacts. if (!isInContacts) { performCallIdentification(theCall); } }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pCSB")); } // Finally, after all user interaction is complete, we execute this code to finish setting // up the outgoing call. The inner method always returns a completed future containing the // call that we've finished setting up. Loading Loading @@ -1508,6 +1537,50 @@ public class CallsManager extends Call.ListenerBase return mLatestPostSelectionProcessingFuture; } /** * Performs call identification for an outgoing phone call. * @param theCall The outgoing call to perform identification. */ private void performCallIdentification(Call theCall) { // Find the user chosen call screening app. String callScreeningApp = mRoleManagerAdapter.getDefaultCallScreeningApp(); CompletableFuture<CallIdentification> future = new CallScreeningServiceHelper(mContext, mLock, callScreeningApp, new ParcelableCallUtils.Converter(), mCurrentUserHandle, theCall, new CallScreeningServiceHelper.AppLabelProxy() { @Override public String getAppLabel(String packageName) { PackageManager pm = mContext.getPackageManager(); try { ApplicationInfo info = pm.getApplicationInfo( packageName, 0); return (String) pm.getApplicationLabel(info); } catch (PackageManager.NameNotFoundException nnfe) { Log.w(this, "Could not determine package name."); } return null; } }).process(); // When we are done, apply call identification to the call. future.thenApply(v -> { Log.i(CallsManager.this, "setting caller ID: %s", v); if (v != null) { synchronized (mLock) { theCall.setCallIdentification(v); } } return null; }); } /** * Finds the {@link PhoneAccountHandle}(s) which could potentially be used to place an outgoing * call. Takes into account the following: Loading src/com/android/server/telecom/ParcelableCallUtils.java +25 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.server.telecom; import static android.telecom.Call.Details.DIRECTION_INCOMING; import static android.telecom.Call.Details.DIRECTION_OUTGOING; import static android.telecom.Call.Details.DIRECTION_UNKNOWN; import android.net.Uri; import android.telecom.Connection; import android.telecom.DisconnectCause; Loading Loading @@ -167,6 +171,14 @@ public class ParcelableCallUtils { } ParcelableRttCall rttCall = includeRttCall ? getParcelableRttCall(call) : null; int callDirection; if (call.isIncoming()) { callDirection = DIRECTION_INCOMING; } else if (call.isUnknown()) { callDirection = DIRECTION_UNKNOWN; } else { callDirection = DIRECTION_OUTGOING; } return new ParcelableCall( call.getId(), Loading Loading @@ -195,7 +207,8 @@ public class ParcelableCallUtils { call.getIntentExtras(), call.getExtras(), call.getCreationTimeMillis(), call.getCallIdentification()); call.getCallIdentification(), callDirection); } /** Loading @@ -203,7 +216,7 @@ public class ParcelableCallUtils { * {@link android.telecom.CallScreeningService}. We ONLY expose the following: * <ul> * <li>Call Id (not exposed to public, but needed to associated calls)</li> * <li>Call state</li> * <li>Call directoin</li> * <li>Creation time</li> * <li>Connection time</li> * <li>Handle (phone number)</li> Loading @@ -216,6 +229,14 @@ public class ParcelableCallUtils { public static ParcelableCall toParcelableCallForScreening(Call call) { Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ? call.getHandle() : null; int callDirection; if (call.isIncoming()) { callDirection = DIRECTION_INCOMING; } else if (call.isUnknown()) { callDirection = DIRECTION_UNKNOWN; } else { callDirection = DIRECTION_OUTGOING; } return new ParcelableCall( call.getId(), getParcelableState(call, false /* supportsExternalCalls */), Loading Loading @@ -243,7 +264,8 @@ public class ParcelableCallUtils { null, /* intentExtras */ null, /* callExtras */ call.getCreationTimeMillis(), null /* callIdentification */); null /* callIdentification */, callDirection); } private static int getParcelableState(Call call, boolean supportsExternalCalls) { Loading src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java +7 −13 Original line number Diff line number Diff line Loading @@ -18,14 +18,11 @@ package com.android.server.telecom.callfiltering; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.PersistableBundle; import android.os.UserHandle; import android.provider.CallLog; import android.provider.Settings; import android.telecom.Log; import android.telecom.Logging.Runnable; import android.telecom.TelecomManager; Loading @@ -34,6 +31,7 @@ import android.text.TextUtils; import com.android.internal.telephony.CallerInfo; import com.android.server.telecom.Call; import com.android.server.telecom.CallScreeningServiceHelper; import com.android.server.telecom.CallerInfoLookupHelper; import com.android.server.telecom.CallsManager; import com.android.server.telecom.LogUtils; Loading @@ -51,14 +49,6 @@ import com.android.server.telecom.TelecomSystem; public class CallScreeningServiceController implements IncomingCallFilter.CallFilter, CallScreeningServiceFilter.CallScreeningFilterResultCallback { /** * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an * app. */ public interface AppLabelProxy { String getAppLabel(String packageName); } private final Context mContext; private final CallsManager mCallsManager; private final PhoneAccountRegistrar mPhoneAccountRegistrar; Loading @@ -66,7 +56,7 @@ public class CallScreeningServiceController implements IncomingCallFilter.CallFi private final TelecomSystem.SyncRoot mTelecomLock; private final TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter; private final CallerInfoLookupHelper mCallerInfoLookupHelper; private final AppLabelProxy mAppLabelProxy; private final CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy; private final int CARRIER_CALL_FILTERING_TIMED_OUT = 2000; // 2 seconds private final int CALL_FILTERING_TIMED_OUT = 4500; // 4.5 seconds Loading Loading @@ -96,7 +86,7 @@ public class CallScreeningServiceController implements IncomingCallFilter.CallFi TelecomSystem.SyncRoot lock, TelecomServiceImpl.SettingsSecureAdapter settingsSecureAdapter, CallerInfoLookupHelper callerInfoLookupHelper, AppLabelProxy appLabelProxy) { CallScreeningServiceHelper.AppLabelProxy appLabelProxy) { mContext = context; mCallsManager = callsManager; mPhoneAccountRegistrar = phoneAccountRegistrar; Loading Loading @@ -144,6 +134,8 @@ public class CallScreeningServiceController implements IncomingCallFilter.CallFi } } else if (!TextUtils.isEmpty(packageName) && packageName.equals(getDefaultDialerPackageName())) { // Default dialer defined CallScreeningService cannot skip the call log. mResult.shouldAddToCallLog = true; mIsDefaultDialerFinished = true; if (result.mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE || mIsUserChosenFinished) { Loading @@ -151,6 +143,8 @@ public class CallScreeningServiceController implements IncomingCallFilter.CallFi } } else if (!TextUtils.isEmpty(packageName) && packageName.equals(getUserChosenPackageName())) { // User defined CallScreeningService cannot skip the call log. mResult.shouldAddToCallLog = true; mIsUserChosenFinished = true; if (mIsDefaultDialerFinished) { finishCallScreening(); Loading Loading
src/com/android/server/telecom/CallScreeningServiceHelper.java 0 → 100644 +247 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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.server.telecom; import android.Manifest; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.telecom.CallIdentification; import android.telecom.CallScreeningService; import android.telecom.Log; import android.telecom.Logging.Session; import android.text.TextUtils; import com.android.internal.telecom.ICallScreeningAdapter; import com.android.internal.telecom.ICallScreeningService; import java.util.List; import java.util.concurrent.CompletableFuture; /** * Helper class for performing operations with {@link CallScreeningService}s. */ public class CallScreeningServiceHelper { private static final String TAG = CallScreeningServiceHelper.class.getSimpleName(); /** * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an * app. */ public interface AppLabelProxy { String getAppLabel(String packageName); } /** * Implementation of {@link CallScreeningService} adapter AIDL; provides a means for responses * from the call screening service to be handled. */ private class CallScreeningAdapter extends ICallScreeningAdapter.Stub { @Override public void allowCall(String s) throws RemoteException { // no-op; we don't allow this on outgoing calls. } @Override public void disallowCall(String s, boolean b, boolean b1, boolean b2, ComponentName componentName) throws RemoteException { // no-op; we don't allow this on outgoing calls. } @Override public void provideCallIdentification(String callId, CallIdentification callIdentification) throws RemoteException { Log.startSession("CSA.pCI"); long token = Binder.clearCallingIdentity(); try { synchronized (mTelecomLock) { if (mCall != null && mCall.getId().equals(callId)) { Log.i(TAG, "provideCallIdentification - got call ID"); callIdentification.setCallScreeningAppName(mAppLabelProxy.getAppLabel( mPackageName)); callIdentification.setCallScreeningPackageName(mPackageName); mFuture.complete(callIdentification); } } } finally { Binder.restoreCallingIdentity(token); Log.endSession(); } mFuture.complete(null); } } private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter; private final TelecomSystem.SyncRoot mTelecomLock; private final Call mCall; private final UserHandle mUserHandle; private final Context mContext; private final AppLabelProxy mAppLabelProxy; private final Session mLoggingSession; private CompletableFuture<CallIdentification> mFuture; private String mPackageName; public CallScreeningServiceHelper(Context context, TelecomSystem.SyncRoot telecomLock, String packageName, ParcelableCallUtils.Converter converter, UserHandle userHandle, Call call, AppLabelProxy appLabelProxy) { mContext = context; mTelecomLock = telecomLock; mParcelableCallUtilsConverter = converter; mCall = call; mUserHandle = userHandle; mPackageName = packageName; mAppLabelProxy = appLabelProxy; mLoggingSession = Log.createSubsession(); } /** * Builds a {@link CompletableFuture} which performs a bind to a {@link CallScreeningService} * @return */ public CompletableFuture<CallIdentification> process() { Log.d(this, "process"); return bindAndGetCallIdentification(); } public CompletableFuture<CallIdentification> bindAndGetCallIdentification() { Log.d(this, "bindAndGetCallIdentification"); if (mPackageName == null) { return CompletableFuture.completedFuture(null); } mFuture = new CompletableFuture<>(); ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { ICallScreeningService screeningService = ICallScreeningService.Stub.asInterface(service); Log.continueSession(mLoggingSession, "CSSH.oSC"); try { try { screeningService.screenCall(new CallScreeningAdapter(), mParcelableCallUtilsConverter.toParcelableCallForScreening(mCall)); } catch (RemoteException e) { Log.w(CallScreeningServiceHelper.this, "Cancelling call id due to remote exception"); mFuture.complete(null); } } finally { Log.endSession(); } } @Override public void onServiceDisconnected(ComponentName name) { // No locking needed -- CompletableFuture only lets one thread call complete. Log.continueSession(mLoggingSession, "CSSH.oSD"); try { if (!mFuture.isDone()) { Log.w(CallScreeningServiceHelper.this, "Cancelling outgoing call screen due to service disconnect."); } mFuture.complete(null); } finally { Log.endSession(); } } }; if (!bindCallScreeningService(mContext, mUserHandle, mPackageName, serviceConnection)) { Log.i(this, "bindAndGetCallIdentification - bind failed"); Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName); mFuture.complete(null); } // Set up a timeout so that we're not waiting forever for the caller ID information. Handler handler = new Handler(); handler.postDelayed(() -> { // No locking needed -- CompletableFuture only lets one thread call complete. Log.continueSession(mLoggingSession, "CSSH.timeout"); try { if (!mFuture.isDone()) { Log.w(TAG, "Cancelling call id process due to timeout"); } mFuture.complete(null); } finally { Log.endSession(); } }, Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver())); return mFuture; } /** * Binds to a {@link CallScreeningService}. * @param context The current context. * @param userHandle User to bind as. * @param packageName Package name of the {@link CallScreeningService}. * @param serviceConnection The {@link ServiceConnection} to be notified of binding. * @return {@code true} if binding succeeds, {@code false} otherwise. */ public static boolean bindCallScreeningService(Context context, UserHandle userHandle, String packageName, ServiceConnection serviceConnection) { if (TextUtils.isEmpty(packageName)) { Log.i(TAG, "PackageName is empty. Not performing call screening."); return false; } Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE) .setPackage(packageName); List<ResolveInfo> entries = context.getPackageManager().queryIntentServicesAsUser( intent, 0, userHandle.getIdentifier()); if (entries.isEmpty()) { Log.i(TAG, packageName + " has no call screening service defined."); return false; } ResolveInfo entry = entries.get(0); if (entry.serviceInfo == null) { Log.w(TAG, packageName + " call screening service has invalid service info"); return false; } if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals( Manifest.permission.BIND_SCREENING_SERVICE)) { Log.w(TAG, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " + entry.serviceInfo.packageName); return false; } ComponentName componentName = new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name); intent.setComponent(componentName); if (context.bindServiceAsUser( intent, serviceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, UserHandle.CURRENT)) { Log.d(TAG, "bindService, found service, waiting for it to connect"); return true; } return false; } }
src/com/android/server/telecom/CallerInfoLookupHelper.java +43 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.telecom.Log; import android.telecom.Logging.Runnable; import android.telecom.Logging.Session; import android.text.TextUtils; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CallerInfo; Loading @@ -37,6 +38,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; public class CallerInfoLookupHelper { public interface OnQueryCompleteListener { Loading Loading @@ -77,6 +79,47 @@ public class CallerInfoLookupHelper { mLock = lock; } /** * Generates a CompletableFuture which performs a contacts lookup asynchronously. The future * returns a {@link Pair} containing the original handle which is being looked up and any * {@link CallerInfo} which was found. * @param handle * @return {@link CompletableFuture} to perform the contacts lookup. */ public CompletableFuture<Pair<Uri, CallerInfo>> startLookup(final Uri handle) { // Create the returned future and final CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>(); final String number = handle.getSchemeSpecificPart(); if (TextUtils.isEmpty(number)) { // Nothing to do here, just finish. Log.d(CallerInfoLookupHelper.this, "onCallerInfoQueryComplete - no number; end early"); callerInfoFuture.complete(new Pair<>(handle, null)); return callerInfoFuture; } // Setup a query complete listener which will get the results of the contacts lookup. OnQueryCompleteListener listener = new OnQueryCompleteListener() { @Override public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) { Log.d(CallerInfoLookupHelper.this, "onCallerInfoQueryComplete - found info for %s", Log.piiHandle(handle)); // Got something, so complete the future. callerInfoFuture.complete(new Pair<>(handle, info)); } @Override public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) { // No-op for now; not something this future cares about. } }; // Start async lookup. startLookup(handle, listener); return callerInfoFuture; } public void startLookup(final Uri handle, OnQueryCompleteListener listener) { if (handle == null) { listener.onCallerInfoQueryComplete(handle, null); Loading
src/com/android/server/telecom/CallsManager.java +74 −1 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import android.provider.BlockedNumberContract.SystemContract; import android.provider.CallLog.Calls; import android.provider.Settings; import android.telecom.CallAudioState; import android.telecom.CallIdentification; import android.telecom.Conference; import android.telecom.Connection; import android.telecom.DisconnectCause; Loading Loading @@ -614,7 +615,7 @@ public class CallsManager extends Call.ListenerBase filters.add(new CallScreeningServiceController(mContext, this, mPhoneAccountRegistrar, new ParcelableCallUtils.Converter(), mLock, new TelecomServiceImpl.SettingsSecureAdapterImpl(), mCallerInfoLookupHelper, new CallScreeningServiceController.AppLabelProxy() { new CallScreeningServiceHelper.AppLabelProxy() { @Override public String getAppLabel(String packageName) { PackageManager pm = mContext.getPackageManager(); Loading Loading @@ -1447,6 +1448,34 @@ public class CallsManager extends Call.ListenerBase return mPendingAccountSelection; }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSPA")); // Potentially perform call identification for dialed TEL scheme numbers. if (PhoneAccount.SCHEME_TEL.equals(handle.getScheme())) { // Perform an asynchronous contacts lookup in this stage; ensure post-dial digits are // not included. CompletableFuture<Pair<Uri, CallerInfo>> contactLookupFuture = mCallerInfoLookupHelper.startLookup(Uri.fromParts(handle.getScheme(), PhoneNumberUtils.extractNetworkPortion(handle.getSchemeSpecificPart()), null)); // Once the phone account selection stage has completed, we can handle the results from // that with the contacts lookup in order to determine if we should lookup bind to the // CallScreeningService in order for it to potentially provide caller ID. dialerSelectPhoneAccountFuture.thenAcceptBothAsync(contactLookupFuture, (callPhoneAccountHandlePair, uriCallerInfoPair) -> { Call theCall = callPhoneAccountHandlePair.first; boolean isInContacts = uriCallerInfoPair.second != null && uriCallerInfoPair.second.contactExists; Log.d(CallsManager.this, "outgoingCallIdStage: isInContacts=%s", isInContacts); // We only want to provide a CallScreeningService with a call if its not in // contacts. if (!isInContacts) { performCallIdentification(theCall); } }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pCSB")); } // Finally, after all user interaction is complete, we execute this code to finish setting // up the outgoing call. The inner method always returns a completed future containing the // call that we've finished setting up. Loading Loading @@ -1508,6 +1537,50 @@ public class CallsManager extends Call.ListenerBase return mLatestPostSelectionProcessingFuture; } /** * Performs call identification for an outgoing phone call. * @param theCall The outgoing call to perform identification. */ private void performCallIdentification(Call theCall) { // Find the user chosen call screening app. String callScreeningApp = mRoleManagerAdapter.getDefaultCallScreeningApp(); CompletableFuture<CallIdentification> future = new CallScreeningServiceHelper(mContext, mLock, callScreeningApp, new ParcelableCallUtils.Converter(), mCurrentUserHandle, theCall, new CallScreeningServiceHelper.AppLabelProxy() { @Override public String getAppLabel(String packageName) { PackageManager pm = mContext.getPackageManager(); try { ApplicationInfo info = pm.getApplicationInfo( packageName, 0); return (String) pm.getApplicationLabel(info); } catch (PackageManager.NameNotFoundException nnfe) { Log.w(this, "Could not determine package name."); } return null; } }).process(); // When we are done, apply call identification to the call. future.thenApply(v -> { Log.i(CallsManager.this, "setting caller ID: %s", v); if (v != null) { synchronized (mLock) { theCall.setCallIdentification(v); } } return null; }); } /** * Finds the {@link PhoneAccountHandle}(s) which could potentially be used to place an outgoing * call. Takes into account the following: Loading
src/com/android/server/telecom/ParcelableCallUtils.java +25 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.server.telecom; import static android.telecom.Call.Details.DIRECTION_INCOMING; import static android.telecom.Call.Details.DIRECTION_OUTGOING; import static android.telecom.Call.Details.DIRECTION_UNKNOWN; import android.net.Uri; import android.telecom.Connection; import android.telecom.DisconnectCause; Loading Loading @@ -167,6 +171,14 @@ public class ParcelableCallUtils { } ParcelableRttCall rttCall = includeRttCall ? getParcelableRttCall(call) : null; int callDirection; if (call.isIncoming()) { callDirection = DIRECTION_INCOMING; } else if (call.isUnknown()) { callDirection = DIRECTION_UNKNOWN; } else { callDirection = DIRECTION_OUTGOING; } return new ParcelableCall( call.getId(), Loading Loading @@ -195,7 +207,8 @@ public class ParcelableCallUtils { call.getIntentExtras(), call.getExtras(), call.getCreationTimeMillis(), call.getCallIdentification()); call.getCallIdentification(), callDirection); } /** Loading @@ -203,7 +216,7 @@ public class ParcelableCallUtils { * {@link android.telecom.CallScreeningService}. We ONLY expose the following: * <ul> * <li>Call Id (not exposed to public, but needed to associated calls)</li> * <li>Call state</li> * <li>Call directoin</li> * <li>Creation time</li> * <li>Connection time</li> * <li>Handle (phone number)</li> Loading @@ -216,6 +229,14 @@ public class ParcelableCallUtils { public static ParcelableCall toParcelableCallForScreening(Call call) { Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ? call.getHandle() : null; int callDirection; if (call.isIncoming()) { callDirection = DIRECTION_INCOMING; } else if (call.isUnknown()) { callDirection = DIRECTION_UNKNOWN; } else { callDirection = DIRECTION_OUTGOING; } return new ParcelableCall( call.getId(), getParcelableState(call, false /* supportsExternalCalls */), Loading Loading @@ -243,7 +264,8 @@ public class ParcelableCallUtils { null, /* intentExtras */ null, /* callExtras */ call.getCreationTimeMillis(), null /* callIdentification */); null /* callIdentification */, callDirection); } private static int getParcelableState(Call call, boolean supportsExternalCalls) { Loading
src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java +7 −13 Original line number Diff line number Diff line Loading @@ -18,14 +18,11 @@ package com.android.server.telecom.callfiltering; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.PersistableBundle; import android.os.UserHandle; import android.provider.CallLog; import android.provider.Settings; import android.telecom.Log; import android.telecom.Logging.Runnable; import android.telecom.TelecomManager; Loading @@ -34,6 +31,7 @@ import android.text.TextUtils; import com.android.internal.telephony.CallerInfo; import com.android.server.telecom.Call; import com.android.server.telecom.CallScreeningServiceHelper; import com.android.server.telecom.CallerInfoLookupHelper; import com.android.server.telecom.CallsManager; import com.android.server.telecom.LogUtils; Loading @@ -51,14 +49,6 @@ import com.android.server.telecom.TelecomSystem; public class CallScreeningServiceController implements IncomingCallFilter.CallFilter, CallScreeningServiceFilter.CallScreeningFilterResultCallback { /** * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an * app. */ public interface AppLabelProxy { String getAppLabel(String packageName); } private final Context mContext; private final CallsManager mCallsManager; private final PhoneAccountRegistrar mPhoneAccountRegistrar; Loading @@ -66,7 +56,7 @@ public class CallScreeningServiceController implements IncomingCallFilter.CallFi private final TelecomSystem.SyncRoot mTelecomLock; private final TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter; private final CallerInfoLookupHelper mCallerInfoLookupHelper; private final AppLabelProxy mAppLabelProxy; private final CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy; private final int CARRIER_CALL_FILTERING_TIMED_OUT = 2000; // 2 seconds private final int CALL_FILTERING_TIMED_OUT = 4500; // 4.5 seconds Loading Loading @@ -96,7 +86,7 @@ public class CallScreeningServiceController implements IncomingCallFilter.CallFi TelecomSystem.SyncRoot lock, TelecomServiceImpl.SettingsSecureAdapter settingsSecureAdapter, CallerInfoLookupHelper callerInfoLookupHelper, AppLabelProxy appLabelProxy) { CallScreeningServiceHelper.AppLabelProxy appLabelProxy) { mContext = context; mCallsManager = callsManager; mPhoneAccountRegistrar = phoneAccountRegistrar; Loading Loading @@ -144,6 +134,8 @@ public class CallScreeningServiceController implements IncomingCallFilter.CallFi } } else if (!TextUtils.isEmpty(packageName) && packageName.equals(getDefaultDialerPackageName())) { // Default dialer defined CallScreeningService cannot skip the call log. mResult.shouldAddToCallLog = true; mIsDefaultDialerFinished = true; if (result.mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE || mIsUserChosenFinished) { Loading @@ -151,6 +143,8 @@ public class CallScreeningServiceController implements IncomingCallFilter.CallFi } } else if (!TextUtils.isEmpty(packageName) && packageName.equals(getUserChosenPackageName())) { // User defined CallScreeningService cannot skip the call log. mResult.shouldAddToCallLog = true; mIsUserChosenFinished = true; if (mIsDefaultDialerFinished) { finishCallScreening(); Loading