Call Screening and Caller ID changes

- Fix bug where call screening service doesn't log calls.
Ensure dialer and 3p app screened calls are logged.
- Add ability to bind to a call screening service for outgoing
calls in order for it to provide outgoing caller id.
- Update test call screening service to support incoming and
outgoing calls.
- Refactor some call screening service filter code to make better
reuse of binding logic.

Bug: 63966743
Test: Manual, CTS
Change-Id: I86d440fb45c08da01ce2159441d2b9efc53eb27c
diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java
new file mode 100644
index 0000000..b2f76c4
--- /dev/null
+++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java
@@ -0,0 +1,247 @@
+/*
+ * 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;
+    }
+}
diff --git a/src/com/android/server/telecom/CallerInfoLookupHelper.java b/src/com/android/server/telecom/CallerInfoLookupHelper.java
index 1e0745f..a919921 100644
--- a/src/com/android/server/telecom/CallerInfoLookupHelper.java
+++ b/src/com/android/server/telecom/CallerInfoLookupHelper.java
@@ -27,6 +27,7 @@
 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;
@@ -37,6 +38,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 
 public class CallerInfoLookupHelper {
     public interface OnQueryCompleteListener {
@@ -77,6 +79,47 @@
         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);
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index be9b1dd..aa4cf08 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -46,6 +46,7 @@
 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;
@@ -612,7 +613,7 @@
         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();
@@ -1436,6 +1437,34 @@
                             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.
@@ -1498,6 +1527,50 @@
     }
 
     /**
+     * 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:
      * 1. Any pre-chosen {@link PhoneAccountHandle} which was specified on the
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index fe52f4b..8dab6a6 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -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;
@@ -167,6 +171,14 @@
         }
 
         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(),
@@ -195,7 +207,8 @@
                 call.getIntentExtras(),
                 call.getExtras(),
                 call.getCreationTimeMillis(),
-                call.getCallIdentification());
+                call.getCallIdentification(),
+                callDirection);
     }
 
     /**
@@ -203,7 +216,7 @@
      * {@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>
@@ -216,6 +229,14 @@
     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 */),
@@ -243,7 +264,8 @@
                 null, /* intentExtras */
                 null, /* callExtras */
                 call.getCreationTimeMillis(),
-                null /* callIdentification */);
+                null /* callIdentification */,
+                callDirection);
     }
 
     private static int getParcelableState(Call call, boolean supportsExternalCalls) {
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
index 33055a8..51fe9b4 100644
--- a/src/com/android/server/telecom/RoleManagerAdapterImpl.java
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -26,11 +26,10 @@
 import java.util.stream.Collectors;
 
 public class RoleManagerAdapterImpl implements RoleManagerAdapter {
-    private static final String ROLE_CALL_REDIRECTION_APP = "android.app.role.PROXY_CALLING_APP";
-    private static final String ROLE_CAR_MODE_DIALER = "android.app.role.CAR_MODE_DIALER_APP";
-    private static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING_APP";
-    private static final String ROLE_CALL_COMPANION_APP =
-            "android.app.role.CALL_COMPANION_APP";
+    private static final String ROLE_CALL_REDIRECTION_APP = RoleManager.ROLE_PROXY_CALLING_APP;
+    private static final String ROLE_CAR_MODE_DIALER = RoleManager.ROLE_CAR_MODE_DIALER_APP;
+    private static final String ROLE_CALL_SCREENING = RoleManager.ROLE_CALL_SCREENING_APP;
+    private static final String ROLE_CALL_COMPANION_APP = RoleManager.ROLE_CALL_COMPANION_APP;
 
     private String mOverrideDefaultCallRedirectionApp = null;
     private String mOverrideDefaultCallScreeningApp = null;
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
index 3ca95ff..b72ac0b 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
@@ -18,14 +18,11 @@
 
 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;
@@ -34,6 +31,7 @@
 
 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;
@@ -51,14 +49,6 @@
 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;
@@ -66,7 +56,7 @@
     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
@@ -96,7 +86,7 @@
             TelecomSystem.SyncRoot lock,
             TelecomServiceImpl.SettingsSecureAdapter settingsSecureAdapter,
             CallerInfoLookupHelper callerInfoLookupHelper,
-            AppLabelProxy appLabelProxy) {
+            CallScreeningServiceHelper.AppLabelProxy appLabelProxy) {
         mContext = context;
         mCallsManager = callsManager;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
@@ -144,6 +134,8 @@
                 }
             } 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) {
@@ -151,6 +143,8 @@
                 }
             } else if (!TextUtils.isEmpty(packageName) &&
                     packageName.equals(getUserChosenPackageName())) {
+                // User defined CallScreeningService cannot skip the call log.
+                mResult.shouldAddToCallLog = true;
                 mIsUserChosenFinished = true;
                 if (mIsDefaultDialerFinished) {
                     finishCallScreening();
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index b23cbe9..3ec1569 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -40,6 +40,7 @@
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallScreeningServiceHelper;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.LogUtils;
 import com.android.server.telecom.ParcelableCallUtils;
@@ -220,7 +221,12 @@
         mCallback = callback;
         mPackageName = packageName;
         mAppName = appName;
-        if (!bindService()) {
+
+        mConnection = new CallScreeningServiceConnection();
+        if (!CallScreeningServiceHelper.bindCallScreeningService(mContext,
+                mCallsManager.getCurrentUserHandle(),
+                mPackageName,
+                mConnection)) {
             Log.i(this, "Could not bind to call screening service");
             finishCallScreening();
         }
@@ -233,7 +239,11 @@
 
             if (mConnection != null) {
                 // We still need to call unbind even if the service disconnected.
-                mContext.unbindService(mConnection);
+                try {
+                    mContext.unbindService(mConnection);
+                } catch (IllegalArgumentException ie) {
+                    Log.e(this, ie, "Unbind error");
+                }
                 mConnection = null;
             }
             mService = null;
@@ -241,52 +251,6 @@
         }
     }
 
-    private boolean bindService() {
-        if (TextUtils.isEmpty(mPackageName)) {
-            Log.i(this, "PackageName is empty. Not performing call screening.");
-            return false;
-        }
-
-        Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
-                .setPackage(mPackageName);
-        List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
-                intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
-        if (entries.isEmpty()) {
-            Log.i(this, mPackageName + "is no call screening services installed on this device.");
-            return false;
-        }
-
-        ResolveInfo entry = entries.get(0);
-        if (entry.serviceInfo == null) {
-            Log.w(this, mPackageName + " 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(this, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " +
-                    entry.serviceInfo.packageName);
-            return false;
-        }
-
-        ComponentName componentName =
-                new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
-        Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, componentName);
-        intent.setComponent(componentName);
-        ServiceConnection connection = new CallScreeningServiceConnection();
-        if (mContext.bindServiceAsUser(
-                intent,
-                connection,
-                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
-                UserHandle.CURRENT)) {
-            Log.d(this, "bindService, found service, waiting for it to connect");
-            mConnection = connection;
-            return true;
-        }
-
-        return false;
-    }
-
     private void onServiceBound(ICallScreeningService service) {
         mService = service;
         try {
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index 68ae65c..28f4473 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -106,6 +106,11 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="Accept Handover"/>
+        <Button
+            android:id="@+id/requestCallScreeningRole"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Req CallScreen Role"/>
     </LinearLayout>
 
     <ListView
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index 959b855..49879d1 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -19,6 +19,8 @@
 import android.app.Activity;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.role.RoleManager;
+import android.content.Intent;
 import android.media.AudioAttributes;
 import android.media.RingtoneManager;
 import android.net.Uri;
@@ -48,11 +50,13 @@
  */
 public class SelfManagedCallingActivity extends Activity {
     private static final String TAG = "SelfMgCallActivity";
+    private static final int REQUEST_ID = 1;
     private SelfManagedCallList mCallList = SelfManagedCallList.getInstance();
     private CheckBox mCheckIfPermittedBeforeCalling;
     private Button mPlaceOutgoingCallButton;
     private Button mPlaceIncomingCallButton;
     private Button mHandoverFrom;
+    private Button mRequestCallScreeningRole;
     private RadioButton mUseAcct1Button;
     private RadioButton mUseAcct2Button;
     private CheckBox mHoldableCheckbox;
@@ -126,6 +130,10 @@
         mHandoverFrom.setOnClickListener((v -> {
             initiateHandover();
         }));
+        mRequestCallScreeningRole = (Button) findViewById(R.id.requestCallScreeningRole);
+        mRequestCallScreeningRole.setOnClickListener((v -> {
+            requestCallScreeningRole();
+        }));
 
         mUseAcct1Button = findViewById(R.id.useAcct1Button);
         mUseAcct2Button = findViewById(R.id.useAcct2Button);
@@ -221,4 +229,21 @@
         NotificationManager mgr = getSystemService(NotificationManager.class);
         mgr.createNotificationChannel(channel);
     }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_ID) {
+            if (resultCode == android.app.Activity.RESULT_OK) {
+                Toast.makeText(this, "Call screening role granted.", Toast.LENGTH_SHORT).show();
+            } else {
+                Toast.makeText(this, "Call screening role NOT granted.", Toast.LENGTH_SHORT).show();
+            }
+        }
+    }
+
+    private void requestCallScreeningRole() {
+        RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
+        Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_SCREENING_APP);
+        startActivityForResult(intent, REQUEST_ID);
+    }
 }
\ No newline at end of file
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java b/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
index fb231da..0c0b303 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
@@ -41,9 +41,20 @@
         sTestCallScreeningService = this;
 
         mDetails = callDetails;
-        Intent errorIntent = new Intent(this, CallScreeningActivity.class);
-        errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        startActivity(errorIntent);
+        if (callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING) {
+            Intent errorIntent = new Intent(this, CallScreeningActivity.class);
+            errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startActivity(errorIntent);
+        } else {
+            CallIdentification callIdentification = new CallIdentification.Builder()
+                    .setNuisanceConfidence(CallIdentification.CONFIDENCE_NOT_NUISANCE)
+                    .setName("Janes's Laundry")
+                    .setDescription("1255 DirtySocks Lane")
+                    .setDetails("Open 16 hrs")
+                    .setPhoto(Icon.createWithResource(this, R.drawable.ic_android_black_24dp))
+                    .build();
+            provideCallIdentification(mDetails, callIdentification);
+        }
     }
 
     public void blockCall() {
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
index 0004035..1001d23 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
@@ -35,6 +35,7 @@
 
 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.ParcelableCallUtils;
@@ -80,8 +81,8 @@
     @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
     @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
 
-    CallScreeningServiceController.AppLabelProxy mAppLabelProxy =
-            new CallScreeningServiceController.AppLabelProxy() {
+    CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy =
+            new CallScreeningServiceHelper.AppLabelProxy() {
         @Override
         public String getAppLabel(String packageName) {
             return APP_NAME;
@@ -292,7 +293,7 @@
         verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
                 false, // shouldAllowCall
                 true, // shouldReject
-                false, // shouldAddToCallLog
+                true, // shouldAddToCallLog (we don't allow services to skip call log)
                 true, // shouldShowNotification
                 CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
                 APP_NAME, //callScreeningAppName
@@ -340,7 +341,7 @@
         verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
                 false, // shouldAllowCall
                 true, // shouldReject
-                false, // shouldAddToCallLog
+                true, // shouldAddToCallLog (we don't allow services to skip call log)
                 true, // shouldShowNotification
                 CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
                 APP_NAME, //callScreeningAppName