1 /*
2  * Copyright (C) 2018 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.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SdkConstant;
22 import android.app.Service;
23 import android.content.Intent;
24 import android.net.Uri;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.os.RemoteException;
30 
31 import com.android.internal.os.SomeArgs;
32 import com.android.internal.telecom.ICallRedirectionAdapter;
33 import com.android.internal.telecom.ICallRedirectionService;
34 
35 /**
36  * This service can be implemented to interact between Telecom and its implementor
37  * for making outgoing call with optional redirection/cancellation purposes.
38  *
39  * <p>
40  * Below is an example manifest registration for a {@code CallRedirectionService}.
41  * {@code
42  * <service android:name="your.package.YourCallRedirectionServiceImplementation"
43  *          android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
44  *      <intent-filter>
45  *          <action android:name="android.telecom.CallRedirectionService"/>
46  *      </intent-filter>
47  * </service>
48  * }
49  */
50 public abstract class CallRedirectionService extends Service {
51     /**
52      * The {@link Intent} that must be declared as handled by the service.
53      */
54     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
55     public static final String SERVICE_INTERFACE = "android.telecom.CallRedirectionService";
56 
57     /**
58      * An adapter to inform Telecom the response from the implementor of the Call
59      * Redirection service
60      */
61     private ICallRedirectionAdapter mCallRedirectionAdapter;
62 
63     /**
64      * Telecom calls this method once upon binding to a {@link CallRedirectionService} to inform
65      * it of a new outgoing call which is being placed. Telecom does not request to redirect
66      * emergency calls and does not request to redirect calls with gateway information.
67      *
68      * <p>Telecom will cancel the call if Telecom does not receive a response in 5 seconds from
69      * the implemented {@link CallRedirectionService} set by users.
70      *
71      * <p>The implemented {@link CallRedirectionService} can call {@link #placeCallUnmodified()},
72      * {@link #redirectCall(Uri, PhoneAccountHandle, boolean)}, and {@link #cancelCall()} only
73      * from here. Calls to these methods are assumed by the Telecom framework to be the response
74      * for the phone call for which {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} was
75      * invoked by Telecom. The Telecom framework will only invoke
76      * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} once each time it binds to a
77      * {@link CallRedirectionService}.
78      *
79      * @param handle the phone number dialed by the user, represented in E.164 format if possible
80      * @param initialPhoneAccount the {@link PhoneAccountHandle} on which the call will be placed.
81      * @param allowInteractiveResponse a boolean to tell if the implemented
82      *                                 {@link CallRedirectionService} should allow interactive
83      *                                 responses with users. Will be {@code false} if, for example
84      *                                 the device is in car mode and the user would not be able to
85      *                                 interact with their device.
86      */
onPlaceCall(@onNull Uri handle, @NonNull PhoneAccountHandle initialPhoneAccount, boolean allowInteractiveResponse)87     public abstract void onPlaceCall(@NonNull Uri handle,
88                                      @NonNull PhoneAccountHandle initialPhoneAccount,
89                                      boolean allowInteractiveResponse);
90 
91     /**
92      * Telecom calls this method when times out waiting for the {@link CallRedirectionService} to
93      * call {@link #placeCallUnmodified()}, {@link #redirectCall(Uri, PhoneAccountHandle, boolean)},
94      * or {@link #cancelCall()}
95      */
onRedirectionTimeout()96     public void onRedirectionTimeout() {}
97 
98     /**
99      * The implemented {@link CallRedirectionService} calls this method to response a request
100      * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
101      * no changes are required to the outgoing call, and that the call should be placed as-is.
102      *
103      * <p>This can only be called from implemented
104      * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}. The response corresponds to the
105      * latest request via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
106      *
107      */
placeCallUnmodified()108     public final void placeCallUnmodified() {
109         try {
110             if (mCallRedirectionAdapter == null) {
111                 throw new IllegalStateException("Can only be called from onPlaceCall.");
112             }
113             mCallRedirectionAdapter.placeCallUnmodified();
114         } catch (RemoteException e) {
115             e.rethrowAsRuntimeException();
116         }
117     }
118 
119     /**
120      * The implemented {@link CallRedirectionService} calls this method to response a request
121      * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
122      * changes are required to the phone number or/and {@link PhoneAccountHandle} for the outgoing
123      * call. Telecom will cancel the call if the implemented {@link CallRedirectionService}
124      * replies Telecom a handle for an emergency number.
125      *
126      * <p>This can only be called from implemented
127      * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}. The response corresponds to the
128      * latest request via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
129      *
130      * @param gatewayUri the gateway uri for call redirection.
131      * @param targetPhoneAccount the {@link PhoneAccountHandle} to use when placing the call.
132      * @param confirmFirst Telecom will ask users to confirm the redirection via a yes/no dialog
133      *                     if the confirmFirst is true, and if the redirection request of this
134      *                     response was sent with a true flag of allowInteractiveResponse via
135      *                     {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}
136      */
redirectCall(@onNull Uri gatewayUri, @NonNull PhoneAccountHandle targetPhoneAccount, boolean confirmFirst)137     public final void redirectCall(@NonNull Uri gatewayUri,
138                                    @NonNull PhoneAccountHandle targetPhoneAccount,
139                                    boolean confirmFirst) {
140         try {
141             if (mCallRedirectionAdapter == null) {
142                 throw new IllegalStateException("Can only be called from onPlaceCall.");
143             }
144             mCallRedirectionAdapter.redirectCall(gatewayUri, targetPhoneAccount, confirmFirst);
145         } catch (RemoteException e) {
146             e.rethrowAsRuntimeException();
147         }
148     }
149 
150     /**
151      * The implemented {@link CallRedirectionService} calls this method to response a request
152      * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
153      * an outgoing call should be canceled entirely.
154      *
155      * <p>This can only be called from implemented
156      * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}. The response corresponds to the
157      * latest request via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
158      *
159      */
cancelCall()160     public final void cancelCall() {
161         try {
162             if (mCallRedirectionAdapter == null) {
163                 throw new IllegalStateException("Can only be called from onPlaceCall.");
164             }
165             mCallRedirectionAdapter.cancelCall();
166         } catch (RemoteException e) {
167             e.rethrowAsRuntimeException();
168         }
169     }
170 
171     /**
172      * A handler message to process the attempt to place call with redirection service from Telecom
173      */
174     private static final int MSG_PLACE_CALL = 1;
175 
176     /**
177      * A handler message to process the attempt to notify the operation of redirection service timed
178      * out from Telecom
179      */
180     private static final int MSG_TIMEOUT = 2;
181 
182     /**
183      * A handler to process the attempt to place call with redirection service from Telecom
184      */
185     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
186         @Override
187         public void handleMessage(Message msg) {
188             switch (msg.what) {
189                 case MSG_PLACE_CALL:
190                     SomeArgs args = (SomeArgs) msg.obj;
191                     try {
192                         mCallRedirectionAdapter = (ICallRedirectionAdapter) args.arg1;
193                         onPlaceCall((Uri) args.arg2, (PhoneAccountHandle) args.arg3,
194                                 (boolean) args.arg4);
195                     } finally {
196                         args.recycle();
197                     }
198                     break;
199                 case MSG_TIMEOUT:
200                     onRedirectionTimeout();
201                     break;
202             }
203         }
204     };
205 
206     private final class CallRedirectionBinder extends ICallRedirectionService.Stub {
207 
208         /**
209          * Telecom calls this method to inform the CallRedirectionService of a new outgoing call
210          * which is about to be placed.
211          * @param handle the phone number dialed by the user
212          * @param initialPhoneAccount the URI of the number the user dialed
213          * @param allowInteractiveResponse a boolean to tell if the implemented
214          *                                 {@link CallRedirectionService} should allow interactive
215          *                                 responses with users.
216          */
217         @Override
placeCall(@onNull ICallRedirectionAdapter adapter, @NonNull Uri handle, @NonNull PhoneAccountHandle initialPhoneAccount, boolean allowInteractiveResponse)218         public void placeCall(@NonNull ICallRedirectionAdapter adapter, @NonNull Uri handle,
219                               @NonNull PhoneAccountHandle initialPhoneAccount,
220                               boolean allowInteractiveResponse) {
221             SomeArgs args = SomeArgs.obtain();
222             args.arg1 = adapter;
223             args.arg2 = handle;
224             args.arg3 = initialPhoneAccount;
225             args.arg4 = allowInteractiveResponse;
226             mHandler.obtainMessage(MSG_PLACE_CALL, args).sendToTarget();
227         }
228 
229         /**
230          * Telecom calls this method to inform the CallRedirectionService of the timeout waiting for
231          * it to complete its operation.
232          */
233         @Override
notifyTimeout()234         public void notifyTimeout() {
235             mHandler.obtainMessage(MSG_TIMEOUT).sendToTarget();
236         }
237     }
238 
239     @Override
onBind(@onNull Intent intent)240     public final @Nullable IBinder onBind(@NonNull Intent intent) {
241         return new CallRedirectionBinder();
242     }
243 
244     @Override
onUnbind(@onNull Intent intent)245     public final boolean onUnbind(@NonNull Intent intent) {
246         return false;
247     }
248 }
249