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