• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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