1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.telecom.callfiltering; 18 19 import android.content.Context; 20 import android.os.Handler; 21 import android.os.HandlerThread; 22 import android.telecom.Log; 23 import android.telecom.Logging.Runnable; 24 25 import com.android.server.telecom.Call; 26 import com.android.server.telecom.LoggedHandlerExecutor; 27 import com.android.server.telecom.LogUtils; 28 import com.android.server.telecom.TelecomSystem; 29 import com.android.server.telecom.Timeouts; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.concurrent.CompletableFuture; 34 35 public class IncomingCallFilterGraph { 36 //TODO: Add logging for control flow. 37 public static final String TAG = "IncomingCallFilterGraph"; 38 public static final CallFilteringResult DEFAULT_RESULT = 39 new CallFilteringResult.Builder() 40 .setShouldAllowCall(true) 41 .setShouldReject(false) 42 .setShouldAddToCallLog(true) 43 .setShouldShowNotification(true) 44 .build(); 45 46 private final CallFilterResultCallback mListener; 47 private final Call mCall; 48 private final Handler mHandler; 49 private final HandlerThread mHandlerThread; 50 private final TelecomSystem.SyncRoot mLock; 51 private List<CallFilter> mFiltersList; 52 private CallFilter mCompletionSentinel; 53 private boolean mFinished; 54 private CallFilteringResult mCurrentResult; 55 private Context mContext; 56 private Timeouts.Adapter mTimeoutsAdapter; 57 58 private class PostFilterTask { 59 private final CallFilter mFilter; 60 PostFilterTask(final CallFilter filter)61 public PostFilterTask(final CallFilter filter) { 62 mFilter = filter; 63 } 64 whenDone(CallFilteringResult result)65 public CallFilteringResult whenDone(CallFilteringResult result) { 66 Log.i(TAG, "Filter %s done, result: %s.", mFilter, result); 67 mFilter.result = result; 68 for (CallFilter filter : mFilter.getFollowings()) { 69 if (filter.decrementAndGetIndegree() == 0) { 70 scheduleFilter(filter); 71 } 72 } 73 if (mFilter.equals(mCompletionSentinel)) { 74 synchronized (mLock) { 75 mFinished = true; 76 mListener.onCallFilteringComplete(mCall, result, false); 77 Log.addEvent(mCall, LogUtils.Events.FILTERING_COMPLETED, result); 78 } 79 mHandlerThread.quit(); 80 } 81 return result; 82 } 83 } 84 IncomingCallFilterGraph(Call call, CallFilterResultCallback listener, Context context, Timeouts.Adapter timeoutsAdapter, TelecomSystem.SyncRoot lock)85 public IncomingCallFilterGraph(Call call, CallFilterResultCallback listener, Context context, 86 Timeouts.Adapter timeoutsAdapter, TelecomSystem.SyncRoot lock) { 87 mListener = listener; 88 mCall = call; 89 mFiltersList = new ArrayList<>(); 90 91 mHandlerThread = new HandlerThread(TAG); 92 mHandlerThread.start(); 93 mHandler = new Handler(mHandlerThread.getLooper()); 94 mLock = lock; 95 mFinished = false; 96 mContext = context; 97 mTimeoutsAdapter = timeoutsAdapter; 98 mCurrentResult = DEFAULT_RESULT; 99 } 100 addFilter(CallFilter filter)101 public void addFilter(CallFilter filter) { 102 mFiltersList.add(filter); 103 } 104 performFiltering()105 public void performFiltering() { 106 Log.addEvent(mCall, LogUtils.Events.FILTERING_INITIATED); 107 CallFilter dummyStart = new CallFilter(); 108 mCompletionSentinel = new CallFilter(); 109 110 for (CallFilter filter : mFiltersList) { 111 addEdge(dummyStart, filter); 112 } 113 for (CallFilter filter : mFiltersList) { 114 addEdge(filter, mCompletionSentinel); 115 } 116 addEdge(dummyStart, mCompletionSentinel); 117 118 scheduleFilter(dummyStart); 119 mHandler.postDelayed(new Runnable("ICFG.pF", mLock) { 120 @Override 121 public void loggedRun() { 122 if (!mFinished) { 123 Log.i(this, "Graph timed out when performing filtering."); 124 Log.addEvent(mCall, LogUtils.Events.FILTERING_TIMED_OUT); 125 mListener.onCallFilteringComplete(mCall, mCurrentResult, true); 126 mFinished = true; 127 mHandlerThread.quit(); 128 } 129 for (CallFilter filter : mFiltersList) { 130 // unbind timed out call screening service 131 if (filter instanceof CallScreeningServiceFilter) { 132 ((CallScreeningServiceFilter) filter).unbindCallScreeningService(); 133 } 134 } 135 } 136 }.prepare(), mTimeoutsAdapter.getCallScreeningTimeoutMillis(mContext.getContentResolver())); 137 } 138 scheduleFilter(CallFilter filter)139 private void scheduleFilter(CallFilter filter) { 140 CallFilteringResult result = new CallFilteringResult.Builder() 141 .setShouldAllowCall(true) 142 .setShouldReject(false) 143 .setShouldSilence(false) 144 .setShouldAddToCallLog(true) 145 .setShouldShowNotification(true) 146 .build(); 147 for (CallFilter dependencyFilter : filter.getDependencies()) { 148 result = result.combine(dependencyFilter.getResult()); 149 } 150 mCurrentResult = result; 151 final CallFilteringResult input = result; 152 153 CompletableFuture<CallFilteringResult> startFuture = 154 CompletableFuture.completedFuture(input); 155 PostFilterTask postFilterTask = new PostFilterTask(filter); 156 157 // TODO: improve these filter logging names to be more reflective of the filters that are 158 // executing 159 startFuture.thenComposeAsync(filter::startFilterLookup, 160 new LoggedHandlerExecutor(mHandler, "ICFG.sF", null)) 161 .thenApplyAsync(postFilterTask::whenDone, 162 new LoggedHandlerExecutor(mHandler, "ICFG.sF", null)) 163 .exceptionally((t) -> { 164 Log.e(filter, t, "Encountered exception running filter"); 165 return null; 166 }); 167 Log.i(TAG, "Filter %s scheduled.", filter); 168 } 169 addEdge(CallFilter before, CallFilter after)170 public static void addEdge(CallFilter before, CallFilter after) { 171 before.addFollowings(after); 172 after.addDependency(before); 173 } 174 getHandlerThread()175 public HandlerThread getHandlerThread() { 176 return mHandlerThread; 177 } 178 } 179