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
17package android.telecom;
18
19import android.annotation.SdkConstant;
20import android.app.Service;
21import android.content.Intent;
22import android.os.Handler;
23import android.os.IBinder;
24import android.os.Looper;
25import android.os.Message;
26import android.os.RemoteException;
27
28import com.android.internal.os.SomeArgs;
29import com.android.internal.telecom.ICallScreeningService;
30import 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 */
49public 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
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
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         */
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         */
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         */
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         */
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             */
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             */
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             */
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             */
179            public Builder setSkipNotification(boolean shouldSkipNotification) {
180                mShouldSkipNotification = shouldSkipNotification;
181                return this;
182            }
183
184            public CallResponse build() {
185                return new CallResponse(
186                        mShouldDisallowCall,
187                        mShouldRejectCall,
188                        mShouldSkipCallLog,
189                        mShouldSkipNotification);
190            }
191       }
192    }
193
194    public CallScreeningService() {
195    }
196
197    @Override
198    public IBinder onBind(Intent intent) {
199        Log.v(this, "onBind");
200        return new CallScreeningBinder();
201    }
202
203    @Override
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     */
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     */
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