• 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                     .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