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 android.telecom;
18 
19 import android.annotation.SdkConstant;
20 import android.app.Service;
21 import android.content.Intent;
22 import android.os.Handler;
23 import android.os.IBinder;
24 import android.os.Looper;
25 import android.os.Message;
26 import android.os.RemoteException;
27 
28 import com.android.internal.os.SomeArgs;
29 import com.android.internal.telecom.ICallScreeningService;
30 import com.android.internal.telecom.ICallScreeningAdapter;
31 
32 /**
33  * This service can be implemented by the default dialer (see
34  * {@link TelecomManager#getDefaultDialerPackage()}) to allow or disallow incoming calls before
35  * they are shown to a user.
36  * <p>
37  * Below is an example manifest registration for a {@code CallScreeningService}.
38  * <pre>
39  * {@code
40  * <service android:name="your.package.YourCallScreeningServiceImplementation"
41  *          android:permission="android.permission.BIND_SCREENING_SERVICE">
42  *      <intent-filter>
43  *          <action android:name="android.telecom.CallScreeningService"/>
44  *      </intent-filter>
45  * </service>
46  * }
47  * </pre>
48  */
49 public abstract class CallScreeningService extends Service {
50     /**
51      * The {@link Intent} that must be declared as handled by the service.
52      */
53     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
54     public static final String SERVICE_INTERFACE = "android.telecom.CallScreeningService";
55 
56     private static final int MSG_SCREEN_CALL = 1;
57 
58     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
59         @Override
60         public void handleMessage(Message msg) {
61             switch (msg.what) {
62                 case MSG_SCREEN_CALL:
63                     SomeArgs args = (SomeArgs) msg.obj;
64                     try {
65                         mCallScreeningAdapter = (ICallScreeningAdapter) args.arg1;
66                         onScreenCall(
67                                 Call.Details.createFromParcelableCall((ParcelableCall) args.arg2));
68                     } finally {
69                         args.recycle();
70                     }
71                     break;
72             }
73         }
74     };
75 
76     private final class CallScreeningBinder extends ICallScreeningService.Stub {
77         @Override
screenCall(ICallScreeningAdapter adapter, ParcelableCall call)78         public void screenCall(ICallScreeningAdapter adapter, ParcelableCall call) {
79             Log.v(this, "screenCall");
80             SomeArgs args = SomeArgs.obtain();
81             args.arg1 = adapter;
82             args.arg2 = call;
83             mHandler.obtainMessage(MSG_SCREEN_CALL, args).sendToTarget();
84         }
85     }
86 
87     private ICallScreeningAdapter mCallScreeningAdapter;
88 
89     /*
90      * Information about how to respond to an incoming call.
91      */
92     public static class CallResponse {
93         private final boolean mShouldDisallowCall;
94         private final boolean mShouldRejectCall;
95         private final boolean mShouldSkipCallLog;
96         private final boolean mShouldSkipNotification;
97 
CallResponse( boolean shouldDisallowCall, boolean shouldRejectCall, boolean shouldSkipCallLog, boolean shouldSkipNotification)98         private CallResponse(
99                 boolean shouldDisallowCall,
100                 boolean shouldRejectCall,
101                 boolean shouldSkipCallLog,
102                 boolean shouldSkipNotification) {
103             if (!shouldDisallowCall
104                     && (shouldRejectCall || shouldSkipCallLog || shouldSkipNotification)) {
105                 throw new IllegalStateException("Invalid response state for allowed call.");
106             }
107 
108             mShouldDisallowCall = shouldDisallowCall;
109             mShouldRejectCall = shouldRejectCall;
110             mShouldSkipCallLog = shouldSkipCallLog;
111             mShouldSkipNotification = shouldSkipNotification;
112         }
113 
114         /*
115          * @return Whether the incoming call should be blocked.
116          */
getDisallowCall()117         public boolean getDisallowCall() {
118             return mShouldDisallowCall;
119         }
120 
121         /*
122          * @return Whether the incoming call should be disconnected as if the user had manually
123          * rejected it.
124          */
getRejectCall()125         public boolean getRejectCall() {
126             return mShouldRejectCall;
127         }
128 
129         /*
130          * @return Whether the incoming call should not be displayed in the call log.
131          */
getSkipCallLog()132         public boolean getSkipCallLog() {
133             return mShouldSkipCallLog;
134         }
135 
136         /*
137          * @return Whether a missed call notification should not be shown for the incoming call.
138          */
getSkipNotification()139         public boolean getSkipNotification() {
140             return mShouldSkipNotification;
141         }
142 
143         public static class Builder {
144             private boolean mShouldDisallowCall;
145             private boolean mShouldRejectCall;
146             private boolean mShouldSkipCallLog;
147             private boolean mShouldSkipNotification;
148 
149             /*
150              * Sets whether the incoming call should be blocked.
151              */
setDisallowCall(boolean shouldDisallowCall)152             public Builder setDisallowCall(boolean shouldDisallowCall) {
153                 mShouldDisallowCall = shouldDisallowCall;
154                 return this;
155             }
156 
157             /*
158              * Sets whether the incoming call should be disconnected as if the user had manually
159              * rejected it. This property should only be set to true if the call is disallowed.
160              */
setRejectCall(boolean shouldRejectCall)161             public Builder setRejectCall(boolean shouldRejectCall) {
162                 mShouldRejectCall = shouldRejectCall;
163                 return this;
164             }
165 
166             /*
167              * Sets whether the incoming call should not be displayed in the call log. This property
168              * should only be set to true if the call is disallowed.
169              */
setSkipCallLog(boolean shouldSkipCallLog)170             public Builder setSkipCallLog(boolean shouldSkipCallLog) {
171                 mShouldSkipCallLog = shouldSkipCallLog;
172                 return this;
173             }
174 
175             /*
176              * Sets whether a missed call notification should not be shown for the incoming call.
177              * This property should only be set to true if the call is disallowed.
178              */
setSkipNotification(boolean shouldSkipNotification)179             public Builder setSkipNotification(boolean shouldSkipNotification) {
180                 mShouldSkipNotification = shouldSkipNotification;
181                 return this;
182             }
183 
build()184             public CallResponse build() {
185                 return new CallResponse(
186                         mShouldDisallowCall,
187                         mShouldRejectCall,
188                         mShouldSkipCallLog,
189                         mShouldSkipNotification);
190             }
191        }
192     }
193 
CallScreeningService()194     public CallScreeningService() {
195     }
196 
197     @Override
onBind(Intent intent)198     public IBinder onBind(Intent intent) {
199         Log.v(this, "onBind");
200         return new CallScreeningBinder();
201     }
202 
203     @Override
onUnbind(Intent intent)204     public boolean onUnbind(Intent intent) {
205         Log.v(this, "onUnbind");
206         return false;
207     }
208 
209     /**
210      * Called when a new incoming call is added.
211      * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}
212      * should be called to allow or disallow the call.
213      *
214      * @param callDetails Information about a new incoming call, see {@link Call.Details}.
215      */
onScreenCall(Call.Details callDetails)216     public abstract void onScreenCall(Call.Details callDetails);
217 
218     /**
219      * Responds to the given call, either allowing it or disallowing it.
220      *
221      * @param callDetails The call to allow.
222      * @param response The {@link CallScreeningService.CallResponse} which contains information
223      * about how to respond to a call.
224      */
respondToCall(Call.Details callDetails, CallResponse response)225     public final void respondToCall(Call.Details callDetails, CallResponse response) {
226         try {
227             if (response.getDisallowCall()) {
228                 mCallScreeningAdapter.disallowCall(
229                         callDetails.getTelecomCallId(),
230                         response.getRejectCall(),
231                         !response.getSkipCallLog(),
232                         !response.getSkipNotification());
233             } else {
234                 mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
235             }
236         } catch (RemoteException e) {
237         }
238     }
239 }
240