Loading src/com/android/server/telecom/callfiltering/CallFilter.java 0 → 100644 +77 −0 Original line number Diff line number Diff line /* * 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 java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; public class CallFilter { private List<CallFilter> mDependencies; private List<CallFilter> mFollowings; private int mIndegree; public CallFilteringResult mPriorStageResult; public CallFilteringResult result; private CompletableFuture<CallFilteringResult> mResultFuture; public CallFilter() { mDependencies = new ArrayList<>(); mFollowings = new ArrayList<>(); mPriorStageResult = null; } public CompletionStage<CallFilteringResult> startFilterLookup( CallFilteringResult priorStageResult) { return CompletableFuture.completedFuture(priorStageResult); } List<CallFilter> getDependencies() { return mDependencies; } void addDependency(CallFilter filter) { synchronized (this) { mDependencies.add(filter); mIndegree = mDependencies.size(); } } List<CallFilter> getFollowings() { return mFollowings; } void addFollowings(CallFilter filter) { mFollowings.add(filter); } int decrementAndGetIndegree() { synchronized (this) { mIndegree--; return mIndegree; } } public CallFilteringResult getResult() { if (result == null) { throw new NullPointerException("Result of this filter is null. This filter hasn't " + "finished performing"); } else { return result; } } } src/com/android/server/telecom/callfiltering/CallFilteringResult.java +6 −5 Original line number Diff line number Diff line Loading @@ -79,8 +79,9 @@ public class CallFilteringResult { public CallFilteringResult build() { return new CallFilteringResult(mShouldAllowCall, mShouldReject, mShouldSilence, mShouldAddToCallLog, mShouldShowNotification, mShouldScreenViaAudio, mCallBlockReason, mCallScreeningAppName, mCallScreeningComponentName); mShouldAddToCallLog, mShouldShowNotification, mCallBlockReason, mCallScreeningAppName, mCallScreeningComponentName, mShouldScreenViaAudio); } } Loading @@ -95,9 +96,9 @@ public class CallFilteringResult { public String mCallScreeningComponentName; private CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, boolean shouldScreenViaAudio, int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) { shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName, boolean shouldScreenViaAudio) { this.shouldAllowCall = shouldAllowCall; this.shouldReject = shouldReject; this.shouldSilence = shouldSilence; Loading src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java 0 → 100644 +154 −0 Original line number Diff line number Diff line /* * 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 android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.telecom.Log; import com.android.server.telecom.Call; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; public class IncomingCallFilterGraph { //TODO: Add logging for control flow. public static final String TAG = "IncomingCallFilterGraph"; public static final CallFilteringResult DEFAULT_SCREENING_RESULT = new CallFilteringResult.Builder() .setShouldAllowCall(true) .setShouldReject(false) .setShouldAddToCallLog(true) .setShouldShowNotification(true) .build(); private final CallFilterResultCallback mListener; private final Call mCall; private final Handler mHandler; private final HandlerThread mHandlerThread; private final TelecomSystem.SyncRoot mLock; private List<CallFilter> mFiltersList; private Executor mExecutor; private CallFilter mDummyComplete; private boolean mFinished; private CallFilteringResult mCurrentResult; private Context mContext; private Timeouts.Adapter mTimeoutsAdapter; private class PostFilterTask { private final CallFilter mFilter; public PostFilterTask(final CallFilter filter) { mFilter = filter; } public CallFilteringResult whenDone(CallFilteringResult result) { mFilter.result = result; for (CallFilter filter : mFilter.getFollowings()) { if (filter.decrementAndGetIndegree() == 0) { scheduleFilter(filter); } } if (mFilter.equals(mDummyComplete)) { synchronized (mLock) { mFinished = true; mListener.onCallFilteringComplete(mCall, result); } mHandlerThread.quit(); } return result; } } public IncomingCallFilterGraph(Call call, CallFilterResultCallback listener, Context context, Timeouts.Adapter timeoutsAdapter, TelecomSystem.SyncRoot lock) { mListener = listener; mCall = call; mFiltersList = new ArrayList<>(); mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mExecutor = mHandler::post; mFinished = false; mContext = context; mTimeoutsAdapter = timeoutsAdapter; mCurrentResult = DEFAULT_SCREENING_RESULT; mLock = lock; } public void addFilter(CallFilter filter) { mFiltersList.add(filter); } public void performFiltering() { CallFilter dummyStart = new CallFilter(); mDummyComplete = new CallFilter(); for (CallFilter filter : mFiltersList) { addEdge(dummyStart, filter); } for (CallFilter filter : mFiltersList) { addEdge(filter, mDummyComplete); } addEdge(dummyStart, mDummyComplete); scheduleFilter(dummyStart); mHandler.postDelayed(() -> { synchronized(mLock) { if (!mFinished) { Log.i(this, "Graph timed out when perform filtering."); mListener.onCallFilteringComplete(mCall, mCurrentResult); mFinished = true; mHandlerThread.quit(); } }}, mTimeoutsAdapter.getCallScreeningTimeoutMillis(mContext.getContentResolver())); } private void scheduleFilter(CallFilter filter) { CallFilteringResult result = new CallFilteringResult.Builder() .setShouldAllowCall(true) .setShouldReject(false) .setShouldSilence(false) .setShouldAddToCallLog(true) .setShouldShowNotification(true) .build(); for (CallFilter dependencyFilter : filter.getDependencies()) { result = result.combine(dependencyFilter.getResult()); } mCurrentResult = result; final CallFilteringResult input = result; CompletableFuture<CallFilteringResult> startFuture = CompletableFuture.completedFuture(input); PostFilterTask postFilterTask = new PostFilterTask(filter); startFuture.thenComposeAsync(filter::startFilterLookup, mExecutor) .thenApplyAsync(postFilterTask::whenDone, mExecutor); } public static void addEdge(CallFilter before, CallFilter after) { before.addFollowings(after); after.addDependency(before); } } tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java 0 → 100644 +178 −0 Original line number Diff line number Diff line /* * 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 android.content.ContentResolver; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.test.suitebuilder.annotation.SmallTest; import com.android.server.telecom.Call; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; import com.android.server.telecom.callfiltering.CallFilter; import com.android.server.telecom.callfiltering.CallFilterResultCallback; import com.android.server.telecom.callfiltering.CallFilteringResult; import com.android.server.telecom.callfiltering.IncomingCallFilterGraph; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.when; import org.junit.Before; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; import org.junit.Test; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; @RunWith(JUnit4.class) public class IncomingCallFilterGraphTest extends TelecomTestCase { @Mock private Call mCall; @Mock private Context mContext; @Mock private Timeouts.Adapter mTimeoutsAdapter; private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {}; private static final CallFilteringResult PASS_CALL_RESULT = new CallFilteringResult.Builder() .setShouldAllowCall(true) .setShouldReject(false) .setShouldSilence(false) .setShouldAddToCallLog(true) .setShouldShowNotification(true).build(); private static final CallFilteringResult REJECT_CALL_RESULT = new CallFilteringResult.Builder() .setShouldAllowCall(false) .setShouldReject(true) .setShouldSilence(false) .setShouldAddToCallLog(true) .setShouldShowNotification(true).build(); private final long FILTER_TIMEOUT = 5000; private final long TEST_TIMEOUT = 7000; private final long TIMEOUT_FILTER_SLEEP_TIME = 10000; private class AllowFilter extends CallFilter { @Override public CompletionStage<CallFilteringResult> startFilterLookup( CallFilteringResult priorStageResult) { return CompletableFuture.completedFuture(PASS_CALL_RESULT); } } private class DisallowFilter extends CallFilter { @Override public CompletionStage<CallFilteringResult> startFilterLookup( CallFilteringResult priorStageResult) { return CompletableFuture.completedFuture(REJECT_CALL_RESULT); } } private class TimeoutFilter extends CallFilter { @Override public CompletionStage<CallFilteringResult> startFilterLookup( CallFilteringResult priorStageResult) { HandlerThread handlerThread = new HandlerThread("TimeoutFilter"); handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()); CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>(); handler.postDelayed(() -> resultFuture.complete(PASS_CALL_RESULT), TIMEOUT_FILTER_SLEEP_TIME); return CompletableFuture.completedFuture(PASS_CALL_RESULT); } } @Before @Override public void setUp() throws Exception { super.setUp(); when(mContext.getContentResolver()).thenReturn(null); when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(nullable(ContentResolver.class))) .thenReturn(FILTER_TIMEOUT); } @SmallTest @Test public void testEmptyGraph() throws Exception { CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>(); CallFilterResultCallback listener = (call, result) -> testResult.complete(result); IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext, mTimeoutsAdapter, mLock); graph.performFiltering(); assertEquals(PASS_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); } @SmallTest @Test public void testFiltersPerformOrder() throws Exception { CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>(); CallFilterResultCallback listener = (call, result) -> testResult.complete(result); IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext, mTimeoutsAdapter, mLock); AllowFilter allowFilter = new AllowFilter(); DisallowFilter disallowFilter = new DisallowFilter(); graph.addFilter(allowFilter); graph.addFilter(disallowFilter); IncomingCallFilterGraph.addEdge(allowFilter, disallowFilter); graph.performFiltering(); assertEquals(REJECT_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); } @SmallTest @Test public void testFiltersPerformInParallel() throws Exception { CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>(); CallFilterResultCallback listener = (call, result) -> testResult.complete(result); IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext, mTimeoutsAdapter, mLock); AllowFilter allowFilter1 = new AllowFilter(); AllowFilter allowFilter2 = new AllowFilter(); DisallowFilter disallowFilter = new DisallowFilter(); graph.addFilter(allowFilter1); graph.addFilter(allowFilter2); graph.addFilter(disallowFilter); graph.performFiltering(); assertEquals(REJECT_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); } @SmallTest @Test public void testFiltersTimeout() throws Exception { CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>(); CallFilterResultCallback listener = (call, result) -> testResult.complete(result); IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext, mTimeoutsAdapter, mLock); DisallowFilter disallowFilter = new DisallowFilter(); TimeoutFilter timeoutFilter = new TimeoutFilter(); graph.addFilter(disallowFilter); graph.addFilter(timeoutFilter); IncomingCallFilterGraph.addEdge(disallowFilter, timeoutFilter); graph.performFiltering(); assertEquals(REJECT_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); } } Loading
src/com/android/server/telecom/callfiltering/CallFilter.java 0 → 100644 +77 −0 Original line number Diff line number Diff line /* * 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 java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; public class CallFilter { private List<CallFilter> mDependencies; private List<CallFilter> mFollowings; private int mIndegree; public CallFilteringResult mPriorStageResult; public CallFilteringResult result; private CompletableFuture<CallFilteringResult> mResultFuture; public CallFilter() { mDependencies = new ArrayList<>(); mFollowings = new ArrayList<>(); mPriorStageResult = null; } public CompletionStage<CallFilteringResult> startFilterLookup( CallFilteringResult priorStageResult) { return CompletableFuture.completedFuture(priorStageResult); } List<CallFilter> getDependencies() { return mDependencies; } void addDependency(CallFilter filter) { synchronized (this) { mDependencies.add(filter); mIndegree = mDependencies.size(); } } List<CallFilter> getFollowings() { return mFollowings; } void addFollowings(CallFilter filter) { mFollowings.add(filter); } int decrementAndGetIndegree() { synchronized (this) { mIndegree--; return mIndegree; } } public CallFilteringResult getResult() { if (result == null) { throw new NullPointerException("Result of this filter is null. This filter hasn't " + "finished performing"); } else { return result; } } }
src/com/android/server/telecom/callfiltering/CallFilteringResult.java +6 −5 Original line number Diff line number Diff line Loading @@ -79,8 +79,9 @@ public class CallFilteringResult { public CallFilteringResult build() { return new CallFilteringResult(mShouldAllowCall, mShouldReject, mShouldSilence, mShouldAddToCallLog, mShouldShowNotification, mShouldScreenViaAudio, mCallBlockReason, mCallScreeningAppName, mCallScreeningComponentName); mShouldAddToCallLog, mShouldShowNotification, mCallBlockReason, mCallScreeningAppName, mCallScreeningComponentName, mShouldScreenViaAudio); } } Loading @@ -95,9 +96,9 @@ public class CallFilteringResult { public String mCallScreeningComponentName; private CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, boolean shouldScreenViaAudio, int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) { shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName, boolean shouldScreenViaAudio) { this.shouldAllowCall = shouldAllowCall; this.shouldReject = shouldReject; this.shouldSilence = shouldSilence; Loading
src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java 0 → 100644 +154 −0 Original line number Diff line number Diff line /* * 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 android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.telecom.Log; import com.android.server.telecom.Call; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; public class IncomingCallFilterGraph { //TODO: Add logging for control flow. public static final String TAG = "IncomingCallFilterGraph"; public static final CallFilteringResult DEFAULT_SCREENING_RESULT = new CallFilteringResult.Builder() .setShouldAllowCall(true) .setShouldReject(false) .setShouldAddToCallLog(true) .setShouldShowNotification(true) .build(); private final CallFilterResultCallback mListener; private final Call mCall; private final Handler mHandler; private final HandlerThread mHandlerThread; private final TelecomSystem.SyncRoot mLock; private List<CallFilter> mFiltersList; private Executor mExecutor; private CallFilter mDummyComplete; private boolean mFinished; private CallFilteringResult mCurrentResult; private Context mContext; private Timeouts.Adapter mTimeoutsAdapter; private class PostFilterTask { private final CallFilter mFilter; public PostFilterTask(final CallFilter filter) { mFilter = filter; } public CallFilteringResult whenDone(CallFilteringResult result) { mFilter.result = result; for (CallFilter filter : mFilter.getFollowings()) { if (filter.decrementAndGetIndegree() == 0) { scheduleFilter(filter); } } if (mFilter.equals(mDummyComplete)) { synchronized (mLock) { mFinished = true; mListener.onCallFilteringComplete(mCall, result); } mHandlerThread.quit(); } return result; } } public IncomingCallFilterGraph(Call call, CallFilterResultCallback listener, Context context, Timeouts.Adapter timeoutsAdapter, TelecomSystem.SyncRoot lock) { mListener = listener; mCall = call; mFiltersList = new ArrayList<>(); mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mExecutor = mHandler::post; mFinished = false; mContext = context; mTimeoutsAdapter = timeoutsAdapter; mCurrentResult = DEFAULT_SCREENING_RESULT; mLock = lock; } public void addFilter(CallFilter filter) { mFiltersList.add(filter); } public void performFiltering() { CallFilter dummyStart = new CallFilter(); mDummyComplete = new CallFilter(); for (CallFilter filter : mFiltersList) { addEdge(dummyStart, filter); } for (CallFilter filter : mFiltersList) { addEdge(filter, mDummyComplete); } addEdge(dummyStart, mDummyComplete); scheduleFilter(dummyStart); mHandler.postDelayed(() -> { synchronized(mLock) { if (!mFinished) { Log.i(this, "Graph timed out when perform filtering."); mListener.onCallFilteringComplete(mCall, mCurrentResult); mFinished = true; mHandlerThread.quit(); } }}, mTimeoutsAdapter.getCallScreeningTimeoutMillis(mContext.getContentResolver())); } private void scheduleFilter(CallFilter filter) { CallFilteringResult result = new CallFilteringResult.Builder() .setShouldAllowCall(true) .setShouldReject(false) .setShouldSilence(false) .setShouldAddToCallLog(true) .setShouldShowNotification(true) .build(); for (CallFilter dependencyFilter : filter.getDependencies()) { result = result.combine(dependencyFilter.getResult()); } mCurrentResult = result; final CallFilteringResult input = result; CompletableFuture<CallFilteringResult> startFuture = CompletableFuture.completedFuture(input); PostFilterTask postFilterTask = new PostFilterTask(filter); startFuture.thenComposeAsync(filter::startFilterLookup, mExecutor) .thenApplyAsync(postFilterTask::whenDone, mExecutor); } public static void addEdge(CallFilter before, CallFilter after) { before.addFollowings(after); after.addDependency(before); } }
tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java 0 → 100644 +178 −0 Original line number Diff line number Diff line /* * 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 android.content.ContentResolver; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.test.suitebuilder.annotation.SmallTest; import com.android.server.telecom.Call; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; import com.android.server.telecom.callfiltering.CallFilter; import com.android.server.telecom.callfiltering.CallFilterResultCallback; import com.android.server.telecom.callfiltering.CallFilteringResult; import com.android.server.telecom.callfiltering.IncomingCallFilterGraph; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.when; import org.junit.Before; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; import org.junit.Test; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; @RunWith(JUnit4.class) public class IncomingCallFilterGraphTest extends TelecomTestCase { @Mock private Call mCall; @Mock private Context mContext; @Mock private Timeouts.Adapter mTimeoutsAdapter; private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {}; private static final CallFilteringResult PASS_CALL_RESULT = new CallFilteringResult.Builder() .setShouldAllowCall(true) .setShouldReject(false) .setShouldSilence(false) .setShouldAddToCallLog(true) .setShouldShowNotification(true).build(); private static final CallFilteringResult REJECT_CALL_RESULT = new CallFilteringResult.Builder() .setShouldAllowCall(false) .setShouldReject(true) .setShouldSilence(false) .setShouldAddToCallLog(true) .setShouldShowNotification(true).build(); private final long FILTER_TIMEOUT = 5000; private final long TEST_TIMEOUT = 7000; private final long TIMEOUT_FILTER_SLEEP_TIME = 10000; private class AllowFilter extends CallFilter { @Override public CompletionStage<CallFilteringResult> startFilterLookup( CallFilteringResult priorStageResult) { return CompletableFuture.completedFuture(PASS_CALL_RESULT); } } private class DisallowFilter extends CallFilter { @Override public CompletionStage<CallFilteringResult> startFilterLookup( CallFilteringResult priorStageResult) { return CompletableFuture.completedFuture(REJECT_CALL_RESULT); } } private class TimeoutFilter extends CallFilter { @Override public CompletionStage<CallFilteringResult> startFilterLookup( CallFilteringResult priorStageResult) { HandlerThread handlerThread = new HandlerThread("TimeoutFilter"); handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()); CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>(); handler.postDelayed(() -> resultFuture.complete(PASS_CALL_RESULT), TIMEOUT_FILTER_SLEEP_TIME); return CompletableFuture.completedFuture(PASS_CALL_RESULT); } } @Before @Override public void setUp() throws Exception { super.setUp(); when(mContext.getContentResolver()).thenReturn(null); when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(nullable(ContentResolver.class))) .thenReturn(FILTER_TIMEOUT); } @SmallTest @Test public void testEmptyGraph() throws Exception { CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>(); CallFilterResultCallback listener = (call, result) -> testResult.complete(result); IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext, mTimeoutsAdapter, mLock); graph.performFiltering(); assertEquals(PASS_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); } @SmallTest @Test public void testFiltersPerformOrder() throws Exception { CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>(); CallFilterResultCallback listener = (call, result) -> testResult.complete(result); IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext, mTimeoutsAdapter, mLock); AllowFilter allowFilter = new AllowFilter(); DisallowFilter disallowFilter = new DisallowFilter(); graph.addFilter(allowFilter); graph.addFilter(disallowFilter); IncomingCallFilterGraph.addEdge(allowFilter, disallowFilter); graph.performFiltering(); assertEquals(REJECT_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); } @SmallTest @Test public void testFiltersPerformInParallel() throws Exception { CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>(); CallFilterResultCallback listener = (call, result) -> testResult.complete(result); IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext, mTimeoutsAdapter, mLock); AllowFilter allowFilter1 = new AllowFilter(); AllowFilter allowFilter2 = new AllowFilter(); DisallowFilter disallowFilter = new DisallowFilter(); graph.addFilter(allowFilter1); graph.addFilter(allowFilter2); graph.addFilter(disallowFilter); graph.performFiltering(); assertEquals(REJECT_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); } @SmallTest @Test public void testFiltersTimeout() throws Exception { CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>(); CallFilterResultCallback listener = (call, result) -> testResult.complete(result); IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext, mTimeoutsAdapter, mLock); DisallowFilter disallowFilter = new DisallowFilter(); TimeoutFilter timeoutFilter = new TimeoutFilter(); graph.addFilter(disallowFilter); graph.addFilter(timeoutFilter); IncomingCallFilterGraph.addEdge(disallowFilter, timeoutFilter); graph.performFiltering(); assertEquals(REJECT_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); } }