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 import com.android.server.telecom.flags.FeatureFlags; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.concurrent.CompletableFuture; 35 36 public class IncomingCallFilterGraph { 37 //TODO: Add logging for control flow. 38 public static final String TAG = "IncomingCallFilterGraph"; 39 public static final CallFilteringResult DEFAULT_RESULT = 40 new CallFilteringResult.Builder() 41 .setShouldAllowCall(true) 42 .setShouldReject(false) 43 .setShouldAddToCallLog(true) 44 .setShouldShowNotification(true) 45 .setDndSuppressed(false) 46 .build(); 47 48 private final CallFilterResultCallback mListener; 49 private final Call mCall; 50 private final Handler mHandler; 51 private final HandlerThread mHandlerThread; 52 private final TelecomSystem.SyncRoot mLock; 53 private List<CallFilter> mFiltersList; 54 private CallFilter mCompletionSentinel; 55 private boolean mFinished; 56 private CallFilteringResult mCurrentResult; 57 private Context mContext; 58 private Timeouts.Adapter mTimeoutsAdapter; 59 private final FeatureFlags mFeatureFlags; 60 61 private class PostFilterTask { 62 private final CallFilter mFilter; 63 PostFilterTask(final CallFilter filter)64 public PostFilterTask(final CallFilter filter) { 65 mFilter = filter; 66 } 67 whenDone(CallFilteringResult result)68 public CallFilteringResult whenDone(CallFilteringResult result) { 69 Log.i(TAG, "Filter %s done, result: %s.", mFilter, result); 70 mFilter.result = result; 71 for (CallFilter filter : mFilter.getFollowings()) { 72 if (filter.decrementAndGetIndegree() == 0) { 73 scheduleFilter(filter); 74 } 75 } 76 if (mFilter.equals(mCompletionSentinel)) { 77 synchronized (mLock) { 78 mFinished = true; 79 mListener.onCallFilteringComplete(mCall, result, false); 80 Log.addEvent(mCall, LogUtils.Events.FILTERING_COMPLETED, result); 81 } 82 mHandlerThread.quit(); 83 } 84 return result; 85 } 86 } 87 IncomingCallFilterGraph(Call call, CallFilterResultCallback listener, Context context, Timeouts.Adapter timeoutsAdapter, FeatureFlags featureFlags, TelecomSystem.SyncRoot lock)88 public IncomingCallFilterGraph(Call call, CallFilterResultCallback listener, Context context, 89 Timeouts.Adapter timeoutsAdapter, FeatureFlags featureFlags, 90 TelecomSystem.SyncRoot lock) { 91 mListener = listener; 92 mCall = call; 93 mFiltersList = new ArrayList<>(); 94 mFeatureFlags = featureFlags; 95 mHandlerThread = new HandlerThread(TAG); 96 mHandlerThread.start(); 97 mHandler = new Handler(mHandlerThread.getLooper()); 98 mLock = lock; 99 mFinished = false; 100 mContext = context; 101 mTimeoutsAdapter = timeoutsAdapter; 102 mCurrentResult = DEFAULT_RESULT; 103 } 104 addFilter(CallFilter filter)105 public void addFilter(CallFilter filter) { 106 mFiltersList.add(filter); 107 } 108 performFiltering()109 public void performFiltering() { 110 Log.addEvent(mCall, LogUtils.Events.FILTERING_INITIATED); 111 CallFilter dummyStart = new CallFilter(); 112 mCompletionSentinel = new CallFilter(); 113 114 for (CallFilter filter : mFiltersList) { 115 addEdge(dummyStart, filter); 116 } 117 for (CallFilter filter : mFiltersList) { 118 addEdge(filter, mCompletionSentinel); 119 } 120 addEdge(dummyStart, mCompletionSentinel); 121 122 scheduleFilter(dummyStart); 123 mHandler.postDelayed(new Runnable("ICFG.pF", mLock) { 124 @Override 125 public void loggedRun() { 126 if (!mFinished) { 127 Log.addEvent(mCall, LogUtils.Events.FILTERING_TIMED_OUT); 128 mCurrentResult = onTimeoutCombineFinishedFilters(mFiltersList, mCurrentResult); 129 mListener.onCallFilteringComplete(mCall, mCurrentResult, true); 130 mFinished = true; 131 mHandlerThread.quit(); 132 } 133 for (CallFilter filter : mFiltersList) { 134 // unbind timed out call screening service 135 if (filter instanceof CallScreeningServiceFilter) { 136 ((CallScreeningServiceFilter) filter).unbindCallScreeningService(); 137 } 138 } 139 } 140 }.prepare(), mTimeoutsAdapter.getCallScreeningTimeoutMillis(mContext.getContentResolver())); 141 } 142 143 /** 144 * This helper takes all the call filters that were added to the graph, checks if filters have 145 * finished, and combines the results. 146 * 147 * @param filtersList all the CallFilters that were added to the call 148 * @param currentResult the current call filter result 149 * @return CallFilterResult of the combined finished Filters. 150 */ onTimeoutCombineFinishedFilters( List<CallFilter> filtersList, CallFilteringResult currentResult)151 private CallFilteringResult onTimeoutCombineFinishedFilters( 152 List<CallFilter> filtersList, 153 CallFilteringResult currentResult) { 154 if (!mFeatureFlags.checkCompletedFiltersOnTimeout()) { 155 return currentResult; 156 } 157 for (CallFilter filter : filtersList) { 158 if (filter.result != null) { 159 currentResult = currentResult.combine(filter.result); 160 } 161 } 162 return currentResult; 163 } 164 scheduleFilter(CallFilter filter)165 private void scheduleFilter(CallFilter filter) { 166 CallFilteringResult result = new CallFilteringResult.Builder() 167 .setShouldAllowCall(true) 168 .setShouldReject(false) 169 .setShouldSilence(false) 170 .setShouldAddToCallLog(true) 171 .setShouldShowNotification(true) 172 .setDndSuppressed(false) 173 .build(); 174 for (CallFilter dependencyFilter : filter.getDependencies()) { 175 // When sequential nodes are completed, they are combined progressively. 176 // ex.) node_a --> node_b --> node_c 177 // node_a will combine with node_b before starting node_c 178 result = result.combine(dependencyFilter.getResult()); 179 } 180 mCurrentResult = result; 181 final CallFilteringResult input = result; 182 183 CompletableFuture<CallFilteringResult> startFuture = 184 CompletableFuture.completedFuture(input); 185 PostFilterTask postFilterTask = new PostFilterTask(filter); 186 187 // TODO: improve these filter logging names to be more reflective of the filters that are 188 // executing 189 startFuture.thenComposeAsync(filter::startFilterLookup, 190 new LoggedHandlerExecutor(mHandler, "ICFG.sF", null)) 191 .thenApplyAsync(postFilterTask::whenDone, 192 new LoggedHandlerExecutor(mHandler, "ICFG.sF", null)) 193 .exceptionally((t) -> { 194 Log.e(filter, t, "Encountered exception running filter"); 195 return null; 196 }); 197 Log.i(TAG, "Filter %s scheduled.", filter); 198 } 199 addEdge(CallFilter before, CallFilter after)200 public static void addEdge(CallFilter before, CallFilter after) { 201 before.addFollowings(after); 202 after.addDependency(before); 203 } 204 getHandlerThread()205 public HandlerThread getHandlerThread() { 206 return mHandlerThread; 207 } 208 } 209