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      * The implemented {@link CallRedirectionService} calls this method to response a request
93      * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
94      * no changes are required to the outgoing call, and that the call should be placed as-is.
95      *
96      * <p>This can only be called from implemented
97      * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}. The response corresponds to the
98      * latest request via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
99      *
100      */
placeCallUnmodified()101     public final void placeCallUnmodified() {
102         try {
103             if (mCallRedirectionAdapter == null) {
104                 throw new IllegalStateException("Can only be called from onPlaceCall.");
105             }
106             mCallRedirectionAdapter.placeCallUnmodified();
107         } catch (RemoteException e) {
108             e.rethrowAsRuntimeException();
109         }
110     }
111 
112     /**
113      * The implemented {@link CallRedirectionService} calls this method to response a request
114      * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
115      * changes are required to the phone number or/and {@link PhoneAccountHandle} for the outgoing
116      * call. Telecom will cancel the call if the implemented {@link CallRedirectionService}
117      * replies Telecom a handle for an emergency number.
118      *
119      * <p>This can only be called from implemented
120      * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}. The response corresponds to the
121      * latest request via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
122      *
123      * @param gatewayUri the gateway uri for call redirection.
124      * @param targetPhoneAccount the {@link PhoneAccountHandle} to use when placing the call.
125      * @param confirmFirst Telecom will ask users to confirm the redirection via a yes/no dialog
126      *                     if the confirmFirst is true, and if the redirection request of this
127      *                     response was sent with a true flag of allowInteractiveResponse via
128      *                     {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}
129      */
redirectCall(@onNull Uri gatewayUri, @NonNull PhoneAccountHandle targetPhoneAccount, boolean confirmFirst)130     public final void redirectCall(@NonNull Uri gatewayUri,
131                                    @NonNull PhoneAccountHandle targetPhoneAccount,
132                                    boolean confirmFirst) {
133         try {
134             if (mCallRedirectionAdapter == null) {
135                 throw new IllegalStateException("Can only be called from onPlaceCall.");
136             }
137             mCallRedirectionAdapter.redirectCall(gatewayUri, targetPhoneAccount, confirmFirst);
138         } catch (RemoteException e) {
139             e.rethrowAsRuntimeException();
140         }
141     }
142 
143     /**
144      * The implemented {@link CallRedirectionService} calls this method to response a request
145      * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
146      * an outgoing call should be canceled entirely.
147      *
148      * <p>This can only be called from implemented
149      * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}. The response corresponds to the
150      * latest request via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
151      *
152      */
cancelCall()153     public final void cancelCall() {
154         try {
155             if (mCallRedirectionAdapter == null) {
156                 throw new IllegalStateException("Can only be called from onPlaceCall.");
157             }
158             mCallRedirectionAdapter.cancelCall();
159         } catch (RemoteException e) {
160             e.rethrowAsRuntimeException();
161         }
162     }
163 
164     /**
165      * A handler message to process the attempt to place call with redirection service from Telecom
166      */
167     private static final int MSG_PLACE_CALL = 1;
168 
169     /**
170      * A handler to process the attempt to place call with redirection service from Telecom
171      */
172     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
173         @Override
174         public void handleMessage(Message msg) {
175             switch (msg.what) {
176                 case MSG_PLACE_CALL:
177                     SomeArgs args = (SomeArgs) msg.obj;
178                     try {
179                         mCallRedirectionAdapter = (ICallRedirectionAdapter) args.arg1;
180                         onPlaceCall((Uri) args.arg2, (PhoneAccountHandle) args.arg3,
181                                 (boolean) args.arg4);
182                     } finally {
183                         args.recycle();
184                     }
185                     break;
186             }
187         }
188     };
189 
190     private final class CallRedirectionBinder extends ICallRedirectionService.Stub {
191 
192         /**
193          * Telecom calls this method to inform the CallRedirectionService of a new outgoing call
194          * which is about to be placed.
195          * @param handle the phone number dialed by the user
196          * @param initialPhoneAccount the URI of the number the user dialed
197          * @param allowInteractiveResponse a boolean to tell if the implemented
198          *                                 {@link CallRedirectionService} should allow interactive
199          *                                 responses with users.
200          */
201         @Override
placeCall(@onNull ICallRedirectionAdapter adapter, @NonNull Uri handle, @NonNull PhoneAccountHandle initialPhoneAccount, boolean allowInteractiveResponse)202         public void placeCall(@NonNull ICallRedirectionAdapter adapter, @NonNull Uri handle,
203                               @NonNull PhoneAccountHandle initialPhoneAccount,
204                               boolean allowInteractiveResponse) {
205             SomeArgs args = SomeArgs.obtain();
206             args.arg1 = adapter;
207             args.arg2 = handle;
208             args.arg3 = initialPhoneAccount;
209             args.arg4 = allowInteractiveResponse;
210             mHandler.obtainMessage(MSG_PLACE_CALL, args).sendToTarget();
211         }
212     }
213 
214     @Override
onBind(@onNull Intent intent)215     public final @Nullable IBinder onBind(@NonNull Intent intent) {
216         return new CallRedirectionBinder();
217     }
218 
219     @Override
onUnbind(@onNull Intent intent)220     public final boolean onUnbind(@NonNull Intent intent) {
221         return false;
222     }
223 }
224