1 /*
2  * Copyright (C) 2016 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.provider.CallLog;
20 import android.provider.CallLog.Calls;
21 import android.telecom.CallScreeningService;
22 import android.text.TextUtils;
23 
24 import java.util.Objects;
25 
26 public class CallFilteringResult {
27     public static class Builder {
28         private boolean mShouldAllowCall;
29         private boolean mShouldReject;
30         private boolean mShouldAddToCallLog;
31         private boolean mShouldShowNotification;
32         private boolean mDndSuppressed = false;
33         private boolean mShouldSilence = false;
34         private boolean mShouldScreenViaAudio = false;
35         private boolean mContactExists = false;
36         private int mCallBlockReason = Calls.BLOCK_REASON_NOT_BLOCKED;
37         private CharSequence mCallScreeningAppName = null;
38         private String mCallScreeningComponentName = null;
39         private CallScreeningService.ParcelableCallResponse mCallScreeningResponse = null;
40         private boolean mIsResponseFromSystemDialer = false;
41 
setShouldAllowCall(boolean shouldAllowCall)42         public Builder setShouldAllowCall(boolean shouldAllowCall) {
43             mShouldAllowCall = shouldAllowCall;
44             return this;
45         }
46 
setShouldReject(boolean shouldReject)47         public Builder setShouldReject(boolean shouldReject) {
48             mShouldReject = shouldReject;
49             return this;
50         }
51 
setShouldAddToCallLog(boolean shouldAddToCallLog)52         public Builder setShouldAddToCallLog(boolean shouldAddToCallLog) {
53             mShouldAddToCallLog = shouldAddToCallLog;
54             return this;
55         }
56 
setShouldShowNotification(boolean shouldShowNotification)57         public Builder setShouldShowNotification(boolean shouldShowNotification) {
58             mShouldShowNotification = shouldShowNotification;
59             return this;
60         }
61 
setDndSuppressed(boolean shouldPerformCheck)62         public Builder setDndSuppressed(boolean shouldPerformCheck) {
63             mDndSuppressed = shouldPerformCheck;
64             return this;
65         }
66 
setShouldSilence(boolean shouldSilence)67         public Builder setShouldSilence(boolean shouldSilence) {
68             mShouldSilence = shouldSilence;
69             return this;
70         }
71 
setCallBlockReason(int callBlockReason)72         public Builder setCallBlockReason(int callBlockReason) {
73             mCallBlockReason = callBlockReason;
74             return this;
75         }
76 
setShouldScreenViaAudio(boolean shouldScreenViaAudio)77         public Builder setShouldScreenViaAudio(boolean shouldScreenViaAudio) {
78             mShouldScreenViaAudio = shouldScreenViaAudio;
79             return this;
80         }
81 
setCallScreeningAppName(CharSequence callScreeningAppName)82         public Builder setCallScreeningAppName(CharSequence callScreeningAppName) {
83             mCallScreeningAppName = callScreeningAppName;
84             return this;
85         }
86 
setCallScreeningComponentName(String callScreeningComponentName)87         public Builder setCallScreeningComponentName(String callScreeningComponentName) {
88             mCallScreeningComponentName = callScreeningComponentName;
89             return this;
90         }
91 
setCallScreeningResponse( CallScreeningService.ParcelableCallResponse response, boolean isFromSystemDialer)92         public Builder setCallScreeningResponse(
93                 CallScreeningService.ParcelableCallResponse response, boolean isFromSystemDialer) {
94             mCallScreeningResponse = response;
95             mIsResponseFromSystemDialer = isFromSystemDialer;
96             return this;
97         }
98 
setContactExists(boolean contactExists)99         public Builder setContactExists(boolean contactExists) {
100             mContactExists = contactExists;
101             return this;
102         }
103 
from(CallFilteringResult result)104         public static Builder from(CallFilteringResult result) {
105             return new Builder()
106                     .setShouldAllowCall(result.shouldAllowCall)
107                     .setShouldReject(result.shouldReject)
108                     .setShouldAddToCallLog(result.shouldAddToCallLog)
109                     .setShouldShowNotification(result.shouldShowNotification)
110                     .setDndSuppressed(result.shouldSuppressCallDueToDndStatus)
111                     .setShouldSilence(result.shouldSilence)
112                     .setCallBlockReason(result.mCallBlockReason)
113                     .setShouldScreenViaAudio(result.shouldScreenViaAudio)
114                     .setCallScreeningAppName(result.mCallScreeningAppName)
115                     .setCallScreeningComponentName(result.mCallScreeningComponentName)
116                     .setCallScreeningResponse(result.mCallScreeningResponse,
117                             result.mIsResponseFromSystemDialer)
118                     .setContactExists(result.contactExists);
119         }
120 
build()121         public CallFilteringResult build() {
122             return new CallFilteringResult(mShouldAllowCall, mShouldReject, mShouldSilence,
123                     mShouldAddToCallLog, mShouldShowNotification,
124                     mDndSuppressed, mCallBlockReason, mCallScreeningAppName,
125                     mCallScreeningComponentName, mCallScreeningResponse,
126                     mIsResponseFromSystemDialer, mShouldScreenViaAudio, mContactExists);
127         }
128     }
129 
130     public boolean shouldAllowCall;
131     public boolean shouldReject;
132     public boolean shouldSilence;
133     public boolean shouldAddToCallLog;
134     public boolean shouldScreenViaAudio = false;
135     public boolean shouldShowNotification;
136     public boolean shouldSuppressCallDueToDndStatus = false;
137     public int mCallBlockReason;
138     public CharSequence mCallScreeningAppName;
139     public String mCallScreeningComponentName;
140     public CallScreeningService.ParcelableCallResponse mCallScreeningResponse;
141     public boolean mIsResponseFromSystemDialer;
142     public boolean contactExists;
143 
CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, boolean shouldSuppress, int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName, CallScreeningService.ParcelableCallResponse callScreeningResponse, boolean isResponseFromSystemDialer, boolean shouldScreenViaAudio, boolean contactExists)144     private CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
145             shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, boolean
146             shouldSuppress, int callBlockReason, CharSequence callScreeningAppName,
147             String callScreeningComponentName,
148             CallScreeningService.ParcelableCallResponse callScreeningResponse,
149             boolean isResponseFromSystemDialer,
150             boolean shouldScreenViaAudio, boolean contactExists) {
151         this.shouldAllowCall = shouldAllowCall;
152         this.shouldReject = shouldReject;
153         this.shouldSilence = shouldSilence;
154         this.shouldAddToCallLog = shouldAddToCallLog;
155         this.shouldShowNotification = shouldShowNotification;
156         this.shouldSuppressCallDueToDndStatus = shouldSuppress;
157         this.shouldScreenViaAudio = shouldScreenViaAudio;
158         this.mCallBlockReason = callBlockReason;
159         this.mCallScreeningAppName = callScreeningAppName;
160         this.mCallScreeningComponentName = callScreeningComponentName;
161         this.mCallScreeningResponse = callScreeningResponse;
162         this.mIsResponseFromSystemDialer = isResponseFromSystemDialer;
163         this.contactExists = contactExists;
164     }
165 
166     /**
167      * Combine this CallFilteringResult with another, returning a CallFilteringResult with the more
168      * restrictive properties of the two. Where there are multiple call filtering components which
169      * block a call, the first filter from {@link BlockCheckerFilter},
170      * {@link DirectToVoicemailFilter}, {@link CallScreeningServiceFilter} which blocked a call
171      * shall be used to populate the call block reason, component name, etc.
172      */
combine(CallFilteringResult other)173     public CallFilteringResult combine(CallFilteringResult other) {
174         if (other == null) {
175             return this;
176         }
177 
178         if (isBlockedByProvider(mCallBlockReason)) {
179             return getCombinedCallFilteringResult(other, mCallBlockReason,
180                     null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
181         } else if (isBlockedByProvider(other.mCallBlockReason)) {
182             return getCombinedCallFilteringResult(other, other.mCallBlockReason,
183                     null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
184         }
185 
186         if (mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL
187                 || other.mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL) {
188             return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL,
189                     null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
190         }
191 
192         if (shouldReject && mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE) {
193             return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
194                     mCallScreeningAppName, mCallScreeningComponentName);
195         } else if (other.shouldReject && other.mCallBlockReason == CallLog.Calls
196                 .BLOCK_REASON_CALL_SCREENING_SERVICE) {
197             return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
198                     other.mCallScreeningAppName, other.mCallScreeningComponentName);
199         }
200 
201         if (shouldScreenViaAudio) {
202             return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_NOT_BLOCKED,
203                     mCallScreeningAppName, mCallScreeningComponentName);
204         } else if (other.shouldScreenViaAudio) {
205             return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_NOT_BLOCKED,
206                     other.mCallScreeningAppName, other.mCallScreeningComponentName);
207         }
208 
209         Builder b = new Builder()
210                 .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall)
211                 .setShouldReject(shouldReject || other.shouldReject)
212                 .setShouldSilence(shouldSilence || other.shouldSilence)
213                 .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog)
214                 .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification)
215                 .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio)
216                 .setDndSuppressed(shouldSuppressCallDueToDndStatus
217                         || other.shouldSuppressCallDueToDndStatus)
218                 .setContactExists(contactExists || other.contactExists);
219         combineScreeningResponses(b, this, other);
220         return b.build();
221     }
222 
isBlockedByProvider(int blockReason)223     private boolean isBlockedByProvider(int blockReason) {
224         if (blockReason == Calls.BLOCK_REASON_BLOCKED_NUMBER
225             || blockReason == Calls.BLOCK_REASON_UNKNOWN_NUMBER
226             || blockReason == Calls.BLOCK_REASON_RESTRICTED_NUMBER
227             || blockReason == Calls.BLOCK_REASON_PAY_PHONE
228             || blockReason == Calls.BLOCK_REASON_NOT_IN_CONTACTS) {
229             return true;
230         }
231 
232         return false;
233     }
234 
getCombinedCallFilteringResult(CallFilteringResult other, int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName)235     private CallFilteringResult getCombinedCallFilteringResult(CallFilteringResult other,
236             int callBlockReason, CharSequence callScreeningAppName,
237             String callScreeningComponentName) {
238         Builder b = new Builder()
239                 .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall)
240                 .setShouldReject(shouldReject || other.shouldReject)
241                 .setShouldSilence(shouldSilence || other.shouldSilence)
242                 .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog)
243                 .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification)
244                 .setDndSuppressed(shouldSuppressCallDueToDndStatus
245                         || other.shouldSuppressCallDueToDndStatus)
246                 .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio)
247                 .setCallBlockReason(callBlockReason)
248                 .setCallScreeningAppName(callScreeningAppName)
249                 .setCallScreeningComponentName(callScreeningComponentName)
250                 .setContactExists(contactExists || other.contactExists);
251         combineScreeningResponses(b, this, other);
252         return b.build();
253     }
254 
combineScreeningResponses(Builder builder, CallFilteringResult r1, CallFilteringResult r2)255     private static void combineScreeningResponses(Builder builder, CallFilteringResult r1,
256             CallFilteringResult r2) {
257         if (r1.mIsResponseFromSystemDialer) {
258             builder.setCallScreeningResponse(r1.mCallScreeningResponse, true);
259             builder.setCallScreeningComponentName(r1.mCallScreeningComponentName);
260             builder.setCallScreeningAppName(r1.mCallScreeningAppName);
261         } else if (r2.mIsResponseFromSystemDialer) {
262             builder.setCallScreeningResponse(r2.mCallScreeningResponse, true);
263             builder.setCallScreeningComponentName(r2.mCallScreeningComponentName);
264             builder.setCallScreeningAppName(r2.mCallScreeningAppName);
265         } else {
266             if (r1.mCallScreeningResponse != null) {
267                 builder.setCallScreeningResponse(r1.mCallScreeningResponse, false);
268                 builder.setCallScreeningComponentName(r1.mCallScreeningComponentName);
269                 builder.setCallScreeningAppName(r1.mCallScreeningAppName);
270             } else {
271                 builder.setCallScreeningResponse(r2.mCallScreeningResponse, false);
272                 builder.setCallScreeningComponentName(r2.mCallScreeningComponentName);
273                 builder.setCallScreeningAppName(r2.mCallScreeningAppName);
274             }
275         }
276     }
277 
278     @Override
equals(Object o)279     public boolean equals(Object o) {
280         if (this == o) return true;
281         if (o == null || getClass() != o.getClass()) return false;
282 
283         CallFilteringResult that = (CallFilteringResult) o;
284 
285         if (shouldAllowCall != that.shouldAllowCall) return false;
286         if (shouldReject != that.shouldReject) return false;
287         if (shouldSilence != that.shouldSilence) return false;
288         if (shouldAddToCallLog != that.shouldAddToCallLog) return false;
289         if (shouldShowNotification != that.shouldShowNotification) return false;
290         if (shouldSuppressCallDueToDndStatus != that.shouldSuppressCallDueToDndStatus) return false;
291         if (mCallBlockReason != that.mCallBlockReason) return false;
292         if (contactExists != that.contactExists) return false;
293 
294         if (!Objects.equals(mCallScreeningAppName, that.mCallScreeningAppName)) return false;
295         if (!Objects.equals(mCallScreeningComponentName, that.mCallScreeningComponentName)) {
296             return false;
297         }
298         return true;
299     }
300 
301     @Override
hashCode()302     public int hashCode() {
303         int result = (shouldAllowCall ? 1 : 0);
304         result = 31 * result + (shouldReject ? 1 : 0);
305         result = 31 * result + (shouldSilence ? 1 : 0);
306         result = 31 * result + (shouldAddToCallLog ? 1 : 0);
307         result = 31 * result + (shouldShowNotification ? 1 : 0);
308         return result;
309     }
310 
311     @Override
toString()312     public String toString() {
313         StringBuilder sb = new StringBuilder();
314         sb.append("[");
315         if (shouldAllowCall) {
316             sb.append("Allow");
317         } else if (shouldReject) {
318             sb.append("Reject");
319         } else if (shouldSilence) {
320             sb.append("Silence");
321         } else {
322             sb.append("Ignore");
323         }
324 
325         if (shouldScreenViaAudio) {
326             sb.append(", audio processing");
327         }
328 
329         if (shouldAddToCallLog) {
330             sb.append(", logged");
331         }
332 
333         if (shouldShowNotification) {
334             sb.append(", notified");
335         }
336 
337         if (shouldSuppressCallDueToDndStatus) {
338             sb.append(", DND suppressed");
339         }
340 
341         if (contactExists) {
342             sb.append(", contact exists");
343         }
344 
345         if (mCallBlockReason != 0) {
346             sb.append(", mCallBlockReason = ");
347             sb.append(mCallBlockReason);
348         }
349 
350         if (!TextUtils.isEmpty(mCallScreeningAppName)) {
351             sb.append(", mCallScreeningAppName = ");
352             sb.append(mCallScreeningAppName);
353         }
354 
355         if (!TextUtils.isEmpty(mCallScreeningComponentName)) {
356             sb.append(", mCallScreeningComponentName = ");
357             sb.append(mCallScreeningComponentName);
358         }
359         sb.append("]");
360 
361         return sb.toString();
362     }
363 }
364