Refactored CallScreeningServiceFilter to NewCallScreeningServiceFilter.
moved checking contact exist part to BlockCheckerFiltersince it
need to query caller info and will always perform filtering
before call screen service binding.
Test: TelecomUnitTest
Bug: 135929421
Change-Id: I52c96f2d760446d3444825ec2ad78e6ddc9226c6
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();
+ }
+}