blob: 9fa864e602f9444025f7161165c54f1390e94578 [file] [log] [blame]
/*
* 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 android.telecom.Logging.Runnable;
import com.android.server.telecom.Call;
import com.android.server.telecom.LoggedHandlerExecutor;
import com.android.server.telecom.LogUtils;
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;
public class IncomingCallFilterGraph {
//TODO: Add logging for control flow.
public static final String TAG = "IncomingCallFilterGraph";
public static final CallFilteringResult DEFAULT_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 CallFilter mCompletionSentinel;
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) {
Log.i(TAG, "Filter %s done, result: %s.", mFilter, result);
mFilter.result = result;
for (CallFilter filter : mFilter.getFollowings()) {
if (filter.decrementAndGetIndegree() == 0) {
scheduleFilter(filter);
}
}
if (mFilter.equals(mCompletionSentinel)) {
synchronized (mLock) {
mFinished = true;
mListener.onCallFilteringComplete(mCall, result, false);
Log.addEvent(mCall, LogUtils.Events.FILTERING_COMPLETED, 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());
mLock = lock;
mFinished = false;
mContext = context;
mTimeoutsAdapter = timeoutsAdapter;
mCurrentResult = DEFAULT_RESULT;
}
public void addFilter(CallFilter filter) {
mFiltersList.add(filter);
}
public void performFiltering() {
Log.addEvent(mCall, LogUtils.Events.FILTERING_INITIATED);
CallFilter dummyStart = new CallFilter();
mCompletionSentinel = new CallFilter();
for (CallFilter filter : mFiltersList) {
addEdge(dummyStart, filter);
}
for (CallFilter filter : mFiltersList) {
addEdge(filter, mCompletionSentinel);
}
addEdge(dummyStart, mCompletionSentinel);
scheduleFilter(dummyStart);
mHandler.postDelayed(new Runnable("ICFG.pF", mLock) {
@Override
public void loggedRun() {
if (!mFinished) {
Log.i(this, "Graph timed out when performing filtering.");
Log.addEvent(mCall, LogUtils.Events.FILTERING_TIMED_OUT);
mListener.onCallFilteringComplete(mCall, mCurrentResult, true);
mFinished = true;
mHandlerThread.quit();
}
for (CallFilter filter : mFiltersList) {
// unbind timed out call screening service
if (filter instanceof CallScreeningServiceFilter) {
((CallScreeningServiceFilter) filter).unbindCallScreeningService();
}
}
}
}.prepare(), 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);
// TODO: improve these filter logging names to be more reflective of the filters that are
// executing
startFuture.thenComposeAsync(filter::startFilterLookup,
new LoggedHandlerExecutor(mHandler, "ICFG.sF", null))
.thenApplyAsync(postFilterTask::whenDone,
new LoggedHandlerExecutor(mHandler, "ICFG.sF", null))
.exceptionally((t) -> {
Log.e(filter, t, "Encountered exception running filter");
return null;
});
Log.i(TAG, "Filter %s scheduled.", filter);
}
public static void addEdge(CallFilter before, CallFilter after) {
before.addFollowings(after);
after.addDependency(before);
}
public HandlerThread getHandlerThread() {
return mHandlerThread;
}
}