Refactored CallScreeningServiceFilter to NewCallScreeningServiceFilter.
am: ed0cef05c2

Change-Id: I047bf443df79f638c6b67b50184b89490a4ea26f
diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index 4bad7b3..3f64f2c 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -28,6 +28,7 @@
         private boolean mShouldShowNotification;
         private boolean mShouldSilence = false;
         private boolean mShouldScreenViaAudio = false;
+        private boolean mContactExists = false;
         private int mCallBlockReason = Calls.BLOCK_REASON_NOT_BLOCKED;
         private CharSequence mCallScreeningAppName = null;
         private String mCallScreeningComponentName = null;
@@ -77,11 +78,30 @@
             return this;
         }
 
+        public Builder setContactExists(boolean contactExists) {
+            mContactExists = contactExists;
+            return this;
+        }
+
+        public static Builder from(CallFilteringResult result) {
+            return new Builder()
+                    .setShouldAllowCall(result.shouldAllowCall)
+                    .setShouldReject(result.shouldReject)
+                    .setShouldAddToCallLog(result.shouldAddToCallLog)
+                    .setShouldShowNotification(result.shouldShowNotification)
+                    .setShouldSilence(result.shouldSilence)
+                    .setCallBlockReason(result.mCallBlockReason)
+                    .setShouldScreenViaAudio(result.shouldScreenViaAudio)
+                    .setCallScreeningAppName(result.mCallScreeningAppName)
+                    .setCallScreeningComponentName(result.mCallScreeningComponentName)
+                    .setContactExists(result.contactExists);
+        }
+
         public CallFilteringResult build() {
             return new CallFilteringResult(mShouldAllowCall, mShouldReject, mShouldSilence,
                     mShouldAddToCallLog, mShouldShowNotification, mCallBlockReason,
-                    mCallScreeningAppName, mCallScreeningComponentName,
-                    mShouldScreenViaAudio);
+                    mCallScreeningAppName, mCallScreeningComponentName, mShouldScreenViaAudio,
+                    mContactExists);
         }
     }
 
@@ -94,11 +114,12 @@
     public int mCallBlockReason;
     public CharSequence mCallScreeningAppName;
     public String mCallScreeningComponentName;
+    public boolean contactExists;
 
     private CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
             shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, int
             callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName,
-            boolean shouldScreenViaAudio) {
+            boolean shouldScreenViaAudio, boolean contactExists) {
         this.shouldAllowCall = shouldAllowCall;
         this.shouldReject = shouldReject;
         this.shouldSilence = shouldSilence;
@@ -108,6 +129,7 @@
         this.mCallBlockReason = callBlockReason;
         this.mCallScreeningAppName = callScreeningAppName;
         this.mCallScreeningComponentName = callScreeningComponentName;
+        this.contactExists = contactExists;
     }
 
     /**
@@ -187,6 +209,7 @@
                 .setCallBlockReason(callBlockReason)
                 .setCallScreeningAppName(callScreeningAppName)
                 .setCallScreeningComponentName(callScreeningComponentName)
+                .setContactExists(contactExists || other.contactExists)
                 .build();
     }
 
@@ -204,6 +227,7 @@
         if (shouldAddToCallLog != that.shouldAddToCallLog) return false;
         if (shouldShowNotification != that.shouldShowNotification) return false;
         if (mCallBlockReason != that.mCallBlockReason) return false;
+        if (contactExists != that.contactExists) return false;
 
         if ((TextUtils.isEmpty(mCallScreeningAppName) &&
             TextUtils.isEmpty(that.mCallScreeningAppName)) &&
@@ -258,6 +282,10 @@
             sb.append(", notified");
         }
 
+        if (contactExists) {
+            sb.append(", contact exists");
+        }
+
         if (mCallBlockReason != 0) {
             sb.append(", mCallBlockReason = ");
             sb.append(mCallBlockReason);
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
index fededed..99f4e5d 100644
--- a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
@@ -33,7 +33,7 @@
 public class IncomingCallFilterGraph {
     //TODO: Add logging for control flow.
     public static final String TAG = "IncomingCallFilterGraph";
-    public static final CallFilteringResult DEFAULT_SCREENING_RESULT =
+    public static final CallFilteringResult DEFAULT_RESULT =
             new CallFilteringResult.Builder()
                     .setShouldAllowCall(true)
                     .setShouldReject(false)
@@ -92,8 +92,8 @@
         mFinished = false;
         mContext = context;
         mTimeoutsAdapter = timeoutsAdapter;
-        mCurrentResult = DEFAULT_SCREENING_RESULT;
         mLock = lock;
+        mCurrentResult = DEFAULT_RESULT;
     }
 
     public void addFilter(CallFilter filter) {
diff --git a/src/com/android/server/telecom/callfiltering/NewCallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/NewCallScreeningServiceFilter.java
new file mode 100644
index 0000000..aa81a49
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/NewCallScreeningServiceFilter.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2019 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.callfiltering;
+
+import static com.android.server.telecom.callfiltering.IncomingCallFilterGraph.DEFAULT_RESULT;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.provider.CallLog;
+import android.telecom.Log;
+import android.telecom.TelecomManager;
+
+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.ParcelableCallUtils;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class NewCallScreeningServiceFilter extends CallFilter {
+    //TODO: Change the name of this class with CallScreeningServiceFilter
+    public static final int PACKAGE_TYPE_CARRIER = 0;
+    public static final int PACKAGE_TYPE_DEFAULT_DIALER = 1;
+    public static final int PACKAGE_TYPE_USER_CHOSEN = 2;
+    public static final long CALL_SCREENING_FILTER_TIMEOUT = 5000;
+
+    private final Call mCall;
+    private final String mPackageName;
+    private final int mPackagetype;
+    private PackageManager mPackageManager;
+    private Context mContext;
+    private CallScreeningServiceConnection mConnection;
+    private final CallsManager mCallsManager;
+    private CharSequence mAppName;
+    private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+
+    private class CallScreeningAdapter extends ICallScreeningAdapter.Stub {
+        private CompletableFuture<CallFilteringResult> mResultFuture;
+
+        public CallScreeningAdapter(CompletableFuture<CallFilteringResult> resultFuture) {
+            mResultFuture = resultFuture;
+        }
+
+        @Override
+        public void allowCall(String callId) {
+            Long token = Binder.clearCallingIdentity();
+            if (mCall == null || (!mCall.getId().equals(callId))) {
+                Log.w(this, "allowCall, unknown call id: %s", callId);
+            }
+            mResultFuture.complete(DEFAULT_RESULT);
+            Binder.restoreCallingIdentity(token);
+            unbindCallScreeningService();
+        }
+
+        @Override
+        public void disallowCall(String callId, boolean shouldReject,
+                boolean shouldAddToCallLog, boolean shouldShowNotification,
+                ComponentName componentName) {
+            long token = Binder.clearCallingIdentity();
+            if (mCall != null && mCall.getId().equals(callId)) {
+                mResultFuture.complete(new CallFilteringResult.Builder()
+                        .setShouldAllowCall(false)
+                        .setShouldReject(shouldReject)
+                        .setShouldSilence(false)
+                        .setShouldAddToCallLog(shouldAddToCallLog
+                                || packageTypeShouldAdd(mPackagetype))
+                        .setShouldShowNotification(shouldShowNotification)
+                        .setCallBlockReason(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                        .setCallScreeningAppName(mAppName)
+                        .setCallScreeningComponentName(componentName.flattenToString())
+                        .build());
+            } else {
+                Log.w(this, "disallowCall, unknown call id: %s", callId);
+                mResultFuture.complete(DEFAULT_RESULT);
+            }
+            Binder.restoreCallingIdentity(token);
+            unbindCallScreeningService();
+        }
+
+        @Override
+        public void silenceCall(String callId) {
+            long token = Binder.clearCallingIdentity();
+            if (mCall != null && mCall.getId().equals(callId)) {
+                mResultFuture.complete(new CallFilteringResult.Builder()
+                        .setShouldAllowCall(true)
+                        .setShouldReject(false)
+                        .setShouldSilence(true)
+                        .setShouldAddToCallLog(true)
+                        .setShouldShowNotification(true)
+                        .build());
+            } else {
+                Log.w(this, "silenceCall, unknown call id: %s" , callId);
+                mResultFuture.complete(DEFAULT_RESULT);
+            }
+            Binder.restoreCallingIdentity(token);
+            unbindCallScreeningService();
+        }
+
+        @Override
+        public void screenCallFurther(String callId) {
+            long token = Binder.clearCallingIdentity();
+            if (mPackagetype != PACKAGE_TYPE_DEFAULT_DIALER) {
+                throw new SecurityException("Only the default/system dialer may request screen via"
+                    + "background call audio");
+            }
+            // TODO: add permission check for the additional role-based permission
+
+            if (mCall != null && mCall.getId().equals(callId)) {
+                mResultFuture.complete(new CallFilteringResult.Builder()
+                        .setShouldAllowCall(true)
+                        .setShouldReject(false)
+                        .setShouldSilence(false)
+                        .setShouldScreenViaAudio(true)
+                        .build());
+            } else {
+                Log.w(this, "screenCallFurther, unknown call id: %s", callId);
+                mResultFuture.complete(DEFAULT_RESULT);
+            }
+            Binder.restoreCallingIdentity(token);
+            unbindCallScreeningService();
+        }
+    }
+
+    private class CallScreeningServiceConnection implements ServiceConnection {
+        private CompletableFuture<CallFilteringResult> mResultFuture;
+
+        public CallScreeningServiceConnection(CompletableFuture<CallFilteringResult> resultFuture) {
+            mResultFuture = resultFuture;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder service) {
+            ICallScreeningService callScreeningService =
+                    ICallScreeningService.Stub.asInterface(service);
+
+            try {
+                callScreeningService.screenCall(new CallScreeningAdapter(mResultFuture),
+                        mParcelableCallUtilsConverter.
+                                toParcelableCallForScreening(mCall, isSystemDialer()));
+            } catch (RemoteException e) {
+                Log.e(this, e, "Failed to set the call screening adapter");
+                mResultFuture.complete(DEFAULT_RESULT);
+            }
+            Log.i(this, "Binding completed.");
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName) {
+            mResultFuture.complete(DEFAULT_RESULT);
+            Log.i(this, "Service disconnected.");
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            mResultFuture.complete(DEFAULT_RESULT);
+            Log.i(this, "Binding died.");
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            mResultFuture.complete(DEFAULT_RESULT);
+            Log.i(this, "Null binding.");
+        }
+    }
+
+    public NewCallScreeningServiceFilter(
+            Call call,
+            String packageName,
+            int packageType,
+            Context context,
+            CallsManager callsManager,
+            CallScreeningServiceHelper.AppLabelProxy appLabelProxy,
+            ParcelableCallUtils.Converter parcelableCallUtilsConverter) {
+        super();
+        mCall = call;
+        mPackageName = packageName;
+        mPackagetype = packageType;
+        mContext = context;
+        mPackageManager = mContext.getPackageManager();
+        mCallsManager = callsManager;
+        mAppName = appLabelProxy.getAppLabel(mPackageName);
+        mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
+    }
+
+    @Override
+    public CompletionStage<CallFilteringResult> startFilterLookup(CallFilteringResult priorStageResult) {
+        if (mPackageName == null) {
+            return CompletableFuture.completedFuture(DEFAULT_RESULT);
+        }
+
+        if (!priorStageResult.shouldAllowCall) {
+            // Call already blocked by other filters, no need to bind to call screening service.
+            return CompletableFuture.completedFuture(DEFAULT_RESULT);
+        }
+
+        if (priorStageResult.contactExists && (!hasReadContactsPermission())) {
+            // Binding to the call screening service will be skipped if it does NOT hold
+            // READ_CONTACTS permission and the number is in the user’s contacts
+            return CompletableFuture.completedFuture(DEFAULT_RESULT);
+        }
+
+        CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>();
+
+        bindCallScreeningService(resultFuture);
+        return resultFuture;
+    }
+
+    private boolean hasReadContactsPermission() {
+        int permission = PackageManager.PERMISSION_DENIED;
+        if (mPackagetype == PACKAGE_TYPE_CARRIER || mPackagetype == PACKAGE_TYPE_DEFAULT_DIALER) {
+            permission = PackageManager.PERMISSION_GRANTED;
+        } else if (mPackageManager != null) {
+            permission = mPackageManager.checkPermission(Manifest.permission.READ_CONTACTS,
+                    mPackageName);
+        }
+        return permission == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private void bindCallScreeningService(
+            CompletableFuture<CallFilteringResult> resultFuture) {
+        mConnection = new CallScreeningServiceConnection(resultFuture);
+        if (!CallScreeningServiceHelper.bindCallScreeningService(mContext,
+                mCallsManager.getCurrentUserHandle(), mPackageName, mConnection)) {
+            Log.i(this, "Call screening service binding failed.");
+            resultFuture.complete(DEFAULT_RESULT);
+        }
+    }
+
+    private void unbindCallScreeningService() {
+        if (mConnection != null) {
+            mContext.unbindService(mConnection);
+        }
+        mConnection = null;
+    }
+
+    private boolean isSystemDialer() {
+        if (mPackagetype != PACKAGE_TYPE_DEFAULT_DIALER) {
+            return false;
+        } else {
+            return mPackageName.equals(TelecomManager.from(mContext).getSystemDialerPackage());
+        }
+    }
+
+    private boolean packageTypeShouldAdd(int packageType) {
+        return packageType == PACKAGE_TYPE_CARRIER;
+    }
+
+}
diff --git a/tests/src/com/android/server/telecom/tests/NewCallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/NewCallScreeningServiceFilterTest.java
new file mode 100644
index 0000000..3c6552d
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/NewCallScreeningServiceFilterTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2019 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.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+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.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.provider.CallLog;
+import android.telecom.CallScreeningService;
+import android.telecom.ParcelableCall;
+import android.test.suitebuilder.annotation.SmallTest;
+
+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.ParcelableCallUtils;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.NewCallScreeningServiceFilter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class NewCallScreeningServiceFilterTest extends TelecomTestCase {
+    //TODO: Change the name of this class to CallScreeningServiceFilterTest
+    static @Mock Call mCall;
+    @Mock Context mContext;
+    @Mock PackageManager mPackageManager;
+    @Mock CallsManager mCallsManager;
+    @Mock CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy;
+    @Mock ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+    @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
+    @Mock ICallScreeningService mCallScreeningService;
+    @Mock IBinder mBinder;
+
+    private static final String CALL_ID = "u89prgt9ps78y5";
+    private static final String PKG_NAME = "com.android.services.telecom.tests";
+    private static final String APP_NAME = "TeleTestApp";
+    private static final String CLS_NAME = "CallScreeningService";
+    private static final ComponentName COMPONENT_NAME = new ComponentName(PKG_NAME, CLS_NAME);
+    private ResolveInfo mResolveInfo;
+    private CallFilteringResult inputResult;
+
+    private static final CallFilteringResult PASS_RESULT = new CallFilteringResult.Builder()
+            .setShouldAllowCall(true)
+            .setShouldReject(false)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .build();
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mResolveInfo =  new ResolveInfo() {{
+            serviceInfo = new ServiceInfo();
+            serviceInfo.packageName = PKG_NAME;
+            serviceInfo.name = CLS_NAME;
+            serviceInfo.permission = Manifest.permission.BIND_SCREENING_SERVICE;
+        }};
+        inputResult = new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .setShouldReject(false)
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true)
+                .build();
+
+        when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
+        when(mCall.getId()).thenReturn(CALL_ID);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mAppLabelProxy.getAppLabel(PKG_NAME)).thenReturn(APP_NAME);
+        when(mParcelableCallUtilsConverter.toParcelableCall(
+                eq(mCall), anyBoolean(), eq(mPhoneAccountRegistrar))).thenReturn(null);
+        when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
+                anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+        when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
+                .thenReturn(Collections.singletonList(mResolveInfo));
+        doReturn(mCallScreeningService).when(mBinder).queryLocalInterface(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testNoPackageName() throws Exception {
+        NewCallScreeningServiceFilter filter = new NewCallScreeningServiceFilter(mCall, null,
+                NewCallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+                mAppLabelProxy, mParcelableCallUtilsConverter);
+        assertEquals(PASS_RESULT,
+                filter.startFilterLookup(inputResult).toCompletableFuture().get(
+                        NewCallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+                        TimeUnit.MILLISECONDS));
+    }
+
+    @SmallTest
+    @Test
+    public void testContextFailToBind() throws Exception {
+        when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
+                anyInt(), eq(UserHandle.CURRENT))).thenReturn(false);
+        NewCallScreeningServiceFilter filter = new NewCallScreeningServiceFilter(mCall, PKG_NAME,
+                NewCallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+                mAppLabelProxy, mParcelableCallUtilsConverter);
+        assertEquals(PASS_RESULT,
+                filter.startFilterLookup(inputResult).toCompletableFuture().get(
+                        NewCallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+                        TimeUnit.MILLISECONDS));
+    }
+
+    @SmallTest
+    @Test
+    public void testNoResolveEntries() throws Exception {
+        when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
+                .thenReturn(Collections.emptyList());
+        NewCallScreeningServiceFilter filter = new NewCallScreeningServiceFilter(mCall, PKG_NAME,
+                NewCallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+                mAppLabelProxy, mParcelableCallUtilsConverter);
+        assertEquals(PASS_RESULT,
+                filter.startFilterLookup(inputResult).toCompletableFuture().get(
+                        NewCallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+                        TimeUnit.MILLISECONDS));
+    }
+
+    @SmallTest
+    @Test
+    public void testBadResolveEntry() throws Exception {
+        mResolveInfo.serviceInfo = null;
+        NewCallScreeningServiceFilter filter = new NewCallScreeningServiceFilter(mCall, PKG_NAME,
+                NewCallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+                mAppLabelProxy, mParcelableCallUtilsConverter);
+        assertEquals(PASS_RESULT,
+                filter.startFilterLookup(inputResult).toCompletableFuture().get(
+                        NewCallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+                        TimeUnit.MILLISECONDS));
+    }
+
+    @SmallTest
+    @Test
+    public void testNoBindingCondition() {
+        // Make sure there will be no binding if the package has no READ_CONTACT permission and
+        // contact exist.
+        when(mPackageManager.checkPermission(Manifest.permission.READ_CONTACTS, PKG_NAME))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
+                anyInt(), eq(UserHandle.CURRENT))).thenThrow(new SecurityException());
+        inputResult.contactExists = true;
+        NewCallScreeningServiceFilter filter = new NewCallScreeningServiceFilter(mCall, PKG_NAME,
+                NewCallScreeningServiceFilter.PACKAGE_TYPE_USER_CHOSEN, mContext, mCallsManager,
+                mAppLabelProxy, mParcelableCallUtilsConverter);
+        filter.startFilterLookup(inputResult);
+    }
+
+    @SmallTest
+    @Test
+    public void testBindingCondition() {
+        // Make sure there will be binding if the package has READ_CONTACT permission and contact
+        // exist.
+        inputResult.contactExists = true;
+        NewCallScreeningServiceFilter filter = new NewCallScreeningServiceFilter(mCall, PKG_NAME,
+                NewCallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+                mAppLabelProxy, mParcelableCallUtilsConverter);
+        filter.startFilterLookup(inputResult);
+        ServiceConnection connection = verifyBindingIntent();
+        connection.onServiceDisconnected(COMPONENT_NAME);
+    }
+
+    @SmallTest
+    @Test
+    public void testAllowCall() throws Exception {
+        NewCallScreeningServiceFilter filter = new NewCallScreeningServiceFilter(mCall, PKG_NAME,
+                NewCallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+                mAppLabelProxy, mParcelableCallUtilsConverter);
+        CompletionStage<CallFilteringResult> resultFuture = filter.startFilterLookup(inputResult);
+
+        ServiceConnection serviceConnection = verifyBindingIntent();
+
+        serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+        csAdapter.allowCall(CALL_ID);
+        assertEquals(PASS_RESULT,
+                resultFuture.toCompletableFuture().get(
+                        NewCallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+                        TimeUnit.MILLISECONDS));
+        serviceConnection.onServiceDisconnected(COMPONENT_NAME);
+    }
+
+    @SmallTest
+    @Test
+    public void testDisallowCall() throws Exception {
+        CallFilteringResult expectedResult = new CallFilteringResult.Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setShouldSilence(false)
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true)
+                .setCallBlockReason(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                .setCallScreeningAppName(APP_NAME)
+                .setCallScreeningComponentName(COMPONENT_NAME.flattenToString())
+                .build();
+        NewCallScreeningServiceFilter filter = new NewCallScreeningServiceFilter(mCall, PKG_NAME,
+                NewCallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+                mAppLabelProxy, mParcelableCallUtilsConverter);
+        CompletionStage<CallFilteringResult> resultFuture = filter.startFilterLookup(inputResult);
+
+        ServiceConnection serviceConnection = verifyBindingIntent();
+
+        serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+        csAdapter.disallowCall(CALL_ID,
+                true, // shouldReject
+                true, //shouldAddToCallLog
+                true, // shouldShowNotification
+                COMPONENT_NAME);
+        assertEquals(expectedResult,
+                resultFuture.toCompletableFuture().get(
+                        NewCallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+                        TimeUnit.MILLISECONDS));
+        serviceConnection.onServiceDisconnected(COMPONENT_NAME);
+    }
+
+    @SmallTest
+    @Test
+    public void testSilenceCall() throws Exception {
+        CallFilteringResult expectedResult = new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .setShouldReject(false)
+                .setShouldSilence(true)
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true)
+                .build();
+        NewCallScreeningServiceFilter filter = new NewCallScreeningServiceFilter(mCall, PKG_NAME,
+                NewCallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+                mAppLabelProxy, mParcelableCallUtilsConverter);
+        CompletionStage<CallFilteringResult> resultFuture = filter.startFilterLookup(inputResult);
+
+        ServiceConnection serviceConnection = verifyBindingIntent();
+
+        serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+        csAdapter.silenceCall(CALL_ID);
+        assertEquals(expectedResult,
+                resultFuture.toCompletableFuture().get(
+                        NewCallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+                        TimeUnit.MILLISECONDS));
+
+        serviceConnection.onServiceDisconnected(COMPONENT_NAME);
+    }
+
+    private ServiceConnection verifyBindingIntent() {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ServiceConnection> serviceCaptor = ArgumentCaptor
+                .forClass(ServiceConnection.class);
+        verify(mContext, timeout(NewCallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT))
+                .bindServiceAsUser(intentCaptor.capture(), serviceCaptor.capture(),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.CURRENT));
+
+        Intent capturedIntent = intentCaptor.getValue();
+        assertEquals(CallScreeningService.SERVICE_INTERFACE, capturedIntent.getAction());
+        assertEquals(mResolveInfo.serviceInfo.packageName, capturedIntent.getPackage());
+        assertEquals(new ComponentName(mResolveInfo.serviceInfo.packageName,
+                mResolveInfo.serviceInfo.name), capturedIntent.getComponent());
+
+        return serviceCaptor.getValue();
+    }
+
+    private ICallScreeningAdapter getCallScreeningAdapter() throws Exception {
+        ArgumentCaptor<ICallScreeningAdapter> captor =
+                ArgumentCaptor.forClass(ICallScreeningAdapter.class);
+        verify(mCallScreeningService,
+                timeout(NewCallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT))
+                .screenCall(captor.capture(), nullable(ParcelableCall.class));
+        return captor.getValue();
+    }
+}