• 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 
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                     .setDndSuppressed(false)
45                     .build();
46 
47     private final CallFilterResultCallback mListener;
48     private final Call mCall;
49     private final Handler mHandler;
50     private final HandlerThread mHandlerThread;
51     private final TelecomSystem.SyncRoot mLock;
52     private List<CallFilter> mFiltersList;
53     private CallFilter mCompletionSentinel;
54     private boolean mFinished;
55     private CallFilteringResult mCurrentResult;
56     private Context mContext;
57     private Timeouts.Adapter mTimeoutsAdapter;
58 
59     private class PostFilterTask {
60         private final CallFilter mFilter;
61 
PostFilterTask(final CallFilter filter)62         public PostFilterTask(final CallFilter filter) {
63             mFilter = filter;
64         }
65 
whenDone(CallFilteringResult result)66         public CallFilteringResult whenDone(CallFilteringResult result) {
67             Log.i(TAG, "Filter %s done, result: %s.", mFilter, result);
68             mFilter.result = result;
69             for (CallFilter filter : mFilter.getFollowings()) {
70                 if (filter.decrementAndGetIndegree() == 0) {
71                     scheduleFilter(filter);
72                 }
73             }
74             if (mFilter.equals(mCompletionSentinel)) {
75                 synchronized (mLock) {
76                     mFinished = true;
77                     mListener.onCallFilteringComplete(mCall, result, false);
78                     Log.addEvent(mCall, LogUtils.Events.FILTERING_COMPLETED, result);
79                 }
80                 mHandlerThread.quit();
81             }
82             return result;
83         }
84     }
85 
IncomingCallFilterGraph(Call call, CallFilterResultCallback listener, Context context, Timeouts.Adapter timeoutsAdapter, TelecomSystem.SyncRoot lock)86     public IncomingCallFilterGraph(Call call, CallFilterResultCallback listener, Context context,
87             Timeouts.Adapter timeoutsAdapter, TelecomSystem.SyncRoot lock) {
88         mListener = listener;
89         mCall = call;
90         mFiltersList = new ArrayList<>();
91 
92         mHandlerThread = new HandlerThread(TAG);
93         mHandlerThread.start();
94         mHandler = new Handler(mHandlerThread.getLooper());
95         mLock = lock;
96         mFinished = false;
97         mContext = context;
98         mTimeoutsAdapter = timeoutsAdapter;
99         mCurrentResult = DEFAULT_RESULT;
100     }
101 
addFilter(CallFilter filter)102     public void addFilter(CallFilter filter) {
103         mFiltersList.add(filter);
104     }
105 
performFiltering()106     public void performFiltering() {
107         Log.addEvent(mCall, LogUtils.Events.FILTERING_INITIATED);
108         CallFilter dummyStart = new CallFilter();
109         mCompletionSentinel = new CallFilter();
110 
111         for (CallFilter filter : mFiltersList) {
112             addEdge(dummyStart, filter);
113         }
114         for (CallFilter filter : mFiltersList) {
115             addEdge(filter, mCompletionSentinel);
116         }
117         addEdge(dummyStart, mCompletionSentinel);
118 
119         scheduleFilter(dummyStart);
120         mHandler.postDelayed(new Runnable("ICFG.pF", mLock) {
121             @Override
122             public void loggedRun() {
123                 if (!mFinished) {
124                     Log.i(this, "Graph timed out when performing filtering.");
125                     Log.addEvent(mCall, LogUtils.Events.FILTERING_TIMED_OUT);
126                     mListener.onCallFilteringComplete(mCall, mCurrentResult, true);
127                     mFinished = true;
128                     mHandlerThread.quit();
129                 }
130                 for (CallFilter filter : mFiltersList) {
131                     // unbind timed out call screening service
132                     if (filter instanceof CallScreeningServiceFilter) {
133                         ((CallScreeningServiceFilter) filter).unbindCallScreeningService();
134                     }
135                 }
136             }
137         }.prepare(), mTimeoutsAdapter.getCallScreeningTimeoutMillis(mContext.getContentResolver()));
138     }
139 
scheduleFilter(CallFilter filter)140     private void scheduleFilter(CallFilter filter) {
141         CallFilteringResult result = new CallFilteringResult.Builder()
142                 .setShouldAllowCall(true)
143                 .setShouldReject(false)
144                 .setShouldSilence(false)
145                 .setShouldAddToCallLog(true)
146                 .setShouldShowNotification(true)
147                 .setDndSuppressed(false)
148                 .build();
149         for (CallFilter dependencyFilter : filter.getDependencies()) {
150             result = result.combine(dependencyFilter.getResult());
151         }
152         mCurrentResult = result;
153         final CallFilteringResult input = result;
154 
155         CompletableFuture<CallFilteringResult> startFuture =
156                 CompletableFuture.completedFuture(input);
157         PostFilterTask postFilterTask = new PostFilterTask(filter);
158 
159         // TODO: improve these filter logging names to be more reflective of the filters that are
160         // executing
161         startFuture.thenComposeAsync(filter::startFilterLookup,
162                 new LoggedHandlerExecutor(mHandler, "ICFG.sF", null))
163                 .thenApplyAsync(postFilterTask::whenDone,
164                         new LoggedHandlerExecutor(mHandler, "ICFG.sF", null))
165                 .exceptionally((t) -> {
166                     Log.e(filter, t, "Encountered exception running filter");
167                     return null;
168                 });
169         Log.i(TAG, "Filter %s scheduled.", filter);
170     }
171 
addEdge(CallFilter before, CallFilter after)172     public static void addEdge(CallFilter before, CallFilter after) {
173         before.addFollowings(after);
174         after.addDependency(before);
175     }
176 
getHandlerThread()177     public HandlerThread getHandlerThread() {
178         return mHandlerThread;
179     }
180 }
181