1 /* 2 * Copyright (C) 2017 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 com.googlecode.android_scripting.facade.telephony; 18 19 import java.io.UnsupportedEncodingException; 20 import java.lang.reflect.Field; 21 import java.net.URLEncoder; 22 import java.util.Iterator; 23 import java.util.List; 24 25 import android.app.Service; 26 import android.content.ContentResolver; 27 import android.content.Intent; 28 import android.database.Cursor; 29 import android.telecom.PhoneAccount; 30 import android.telecom.PhoneAccountHandle; 31 import android.telecom.TelecomManager; 32 import android.telecom.VideoProfile; 33 import android.telephony.SubscriptionManager; 34 import android.telephony.TelephonyManager; 35 import android.net.Uri; 36 import android.provider.ContactsContract; 37 38 import com.googlecode.android_scripting.Log; 39 import com.googlecode.android_scripting.facade.AndroidFacade; 40 import com.googlecode.android_scripting.facade.EventFacade; 41 import com.googlecode.android_scripting.facade.FacadeManager; 42 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 43 import com.googlecode.android_scripting.rpc.Rpc; 44 import com.googlecode.android_scripting.rpc.RpcDefault; 45 import com.googlecode.android_scripting.rpc.RpcOptional; 46 import com.googlecode.android_scripting.rpc.RpcParameter; 47 48 /** 49 * Exposes TelecomManager functionality. 50 */ 51 public class TelecomManagerFacade extends RpcReceiver { 52 53 private final Service mService; 54 private final AndroidFacade mAndroidFacade; 55 56 private final TelecomManager mTelecomManager; 57 private final TelephonyManager mTelephonyManager; 58 59 private List<PhoneAccountHandle> mEnabledAccountHandles = null; 60 TelecomManagerFacade(FacadeManager manager)61 public TelecomManagerFacade(FacadeManager manager) { 62 super(manager); 63 mService = manager.getService(); 64 mTelecomManager = new TelecomManager(mService); 65 mTelephonyManager = new TelephonyManager(mService); 66 mAndroidFacade = manager.getReceiver(AndroidFacade.class); 67 InCallServiceImpl.setEventFacade( 68 manager.getReceiver(EventFacade.class)); 69 } 70 71 @Override shutdown()72 public void shutdown() { 73 InCallServiceImpl.setEventFacade(null); 74 } 75 76 @Rpc(description = "If there's a ringing call, accept on behalf of the user.") telecomAcceptRingingCall( @pcParametername = "videoState") @pcOptional String videoState)77 public void telecomAcceptRingingCall( 78 @RpcParameter(name = "videoState") 79 @RpcOptional 80 String videoState) { 81 82 if (videoState == null) { 83 mTelecomManager.acceptRingingCall(); 84 } 85 else { 86 int state = InCallServiceImpl.getVideoCallState(videoState); 87 88 if (state == InCallServiceImpl.STATE_INVALID) { 89 Log.e("telecomAcceptRingingCall: video state is invalid!"); 90 return; 91 } 92 93 mTelecomManager.acceptRingingCall(state); 94 } 95 } 96 97 @Rpc(description = "Removes the missed-call notification if one is present.") telecomCancelMissedCallsNotification()98 public void telecomCancelMissedCallsNotification() { 99 mTelecomManager.cancelMissedCallsNotification(); 100 } 101 102 @Rpc(description = "Remove all Accounts that belong to the calling package from the system.") telecomClearAccounts()103 public void telecomClearAccounts() { 104 mTelecomManager.clearAccounts(); 105 } 106 107 @Rpc(description = "End an ongoing call.") telecomEndCall()108 public Boolean telecomEndCall() { 109 return mTelecomManager.endCall(); 110 } 111 112 @Rpc(description = "Get a list of all PhoneAccounts.") telecomGetAllPhoneAccounts()113 public List<PhoneAccount> telecomGetAllPhoneAccounts() { 114 return mTelecomManager.getAllPhoneAccounts(); 115 } 116 117 @Rpc(description = "Get the current call state.") telecomGetCallState()118 public String telecomGetCallState() { 119 int state = mTelecomManager.getCallState(); 120 return TelephonyUtils.getTelephonyCallStateString(state); 121 } 122 123 @Rpc(description = "Get the current tty mode.") telecomGetCurrentTtyMode()124 public String telecomGetCurrentTtyMode() { 125 int mode = mTelecomManager.getCurrentTtyMode(); 126 return TelephonyUtils.getTtyModeString(mode); 127 } 128 129 @Rpc(description = "Bring incallUI to foreground.") telecomShowInCallScreen( @pcParametername = "showDialpad") @pcOptional @pcDefault"false") Boolean showDialpad)130 public void telecomShowInCallScreen( 131 @RpcParameter(name = "showDialpad") 132 @RpcOptional 133 @RpcDefault("false") 134 Boolean showDialpad) { 135 mTelecomManager.showInCallScreen(showDialpad); 136 } 137 138 @Rpc(description = "Get the list of PhoneAccountHandles with calling capability.") telecomGetEnabledPhoneAccounts()139 public List<PhoneAccountHandle> telecomGetEnabledPhoneAccounts() { 140 mEnabledAccountHandles = mTelecomManager.getCallCapablePhoneAccounts(); 141 return mEnabledAccountHandles; 142 } 143 144 @Rpc(description = "Set the user-chosen default PhoneAccount for making outgoing phone calls.") telecomSetUserSelectedOutgoingPhoneAccount( @pcParametername = "phoneAccountHandleId") String phoneAccountHandleId)145 public void telecomSetUserSelectedOutgoingPhoneAccount( 146 @RpcParameter(name = "phoneAccountHandleId") 147 String phoneAccountHandleId) throws Exception { 148 149 List<PhoneAccountHandle> accountHandles = mTelecomManager 150 .getAllPhoneAccountHandles(); 151 for (PhoneAccountHandle handle : accountHandles) { 152 if (handle.getId().equals(phoneAccountHandleId)) { 153 mTelecomManager.setUserSelectedOutgoingPhoneAccount(handle); 154 Log.d(String.format("Set default Outgoing Phone Account(%s)", 155 phoneAccountHandleId)); 156 return; 157 } 158 } 159 Log.d(String.format( 160 "Failed to find a matching phoneAccountHandleId(%s).", 161 phoneAccountHandleId)); 162 throw new Exception(String.format( 163 "Failed to find a matching phoneAccountHandleId(%s).", 164 phoneAccountHandleId)); 165 } 166 167 @Rpc(description = "Get the user-chosen default PhoneAccount for making outgoing phone calls.") telecomGetUserSelectedOutgoingPhoneAccount()168 public PhoneAccountHandle telecomGetUserSelectedOutgoingPhoneAccount() { 169 return mTelecomManager.getUserSelectedOutgoingPhoneAccount(); 170 } 171 172 @Rpc(description = "Set the PhoneAccount corresponding to user selected subscription id " + 173 " for making outgoing phone calls.") telecomSetUserSelectedOutgoingPhoneAccountBySubId( @pcParametername = "subId") Integer subId)174 public void telecomSetUserSelectedOutgoingPhoneAccountBySubId( 175 @RpcParameter(name = "subId") 176 Integer subId) throws Exception { 177 Iterator<PhoneAccountHandle> phoneAccounts = 178 mTelecomManager.getCallCapablePhoneAccounts().listIterator(); 179 180 while (phoneAccounts.hasNext()) { 181 PhoneAccountHandle phoneAccountHandle = phoneAccounts.next(); 182 PhoneAccount phoneAccount = 183 mTelecomManager.getPhoneAccount(phoneAccountHandle); 184 if (subId == mTelephonyManager.getSubIdForPhoneAccount(phoneAccount)) { 185 mTelecomManager.setUserSelectedOutgoingPhoneAccount(phoneAccountHandle); 186 Log.d(String.format( 187 "Set default Outgoing Phone Account for subscription(%s)", subId)); 188 return; 189 } 190 } 191 Log.d(String.format( 192 "Failed to find a matching Phone Account for subscription (%s).", 193 subId)); 194 throw new Exception(String.format( 195 "Failed to find a matching Phone Account for subscription (%s).", 196 subId)); 197 } 198 199 @Rpc(description = "Returns whether there is an ongoing phone call.") telecomIsInCall()200 public Boolean telecomIsInCall() { 201 return mTelecomManager.isInCall(); 202 } 203 204 @Rpc(description = "Returns whether there is a ringing incoming call.") telecomIsRinging()205 public Boolean telecomIsRinging() { 206 return mTelecomManager.isRinging(); 207 } 208 209 @Rpc(description = "Silences the rigner if there's a ringing call.") telecomSilenceRinger()210 public void telecomSilenceRinger() { 211 mTelecomManager.silenceRinger(); 212 } 213 214 @Rpc(description = "Swap two calls") telecomSwapCalls()215 public void telecomSwapCalls() { 216 // TODO: b/26273475 Add logic to swap the foreground and back ground calls 217 } 218 219 @Rpc(description = "Start listening for added calls") telecomStartListeningForCallAdded()220 public void telecomStartListeningForCallAdded() { 221 InCallServiceImpl.CallListener.startListeningForEvent( 222 InCallServiceImpl.CallListener.LISTEN_CALL_ADDED); 223 } 224 225 @Rpc(description = "Stop listening for added calls") telecomStopListeningForCallAdded()226 public void telecomStopListeningForCallAdded() { 227 InCallServiceImpl.CallListener.stopListeningForEvent( 228 InCallServiceImpl.CallListener.LISTEN_CALL_ADDED); 229 } 230 231 @Rpc(description = "Start listening for removed calls") telecomStartListeningForCallRemoved()232 public void telecomStartListeningForCallRemoved() { 233 InCallServiceImpl.CallListener.startListeningForEvent( 234 InCallServiceImpl.CallListener.LISTEN_CALL_REMOVED); 235 } 236 237 @Rpc(description = "Stop listening for removed calls") telecomStopListeningForCallRemoved()238 public void telecomStopListeningForCallRemoved() { 239 InCallServiceImpl.CallListener.stopListeningForEvent( 240 InCallServiceImpl.CallListener.LISTEN_CALL_REMOVED); 241 } 242 243 @Rpc(description = "Toggles call waiting feature on or off for default voice subscription id.") toggleCallWaiting( @pcParametername = "enabled") @pcOptional Boolean enabled)244 public void toggleCallWaiting( 245 @RpcParameter(name = "enabled") 246 @RpcOptional 247 Boolean enabled) { 248 toggleCallWaitingForSubscription( 249 SubscriptionManager.getDefaultVoiceSubscriptionId(), enabled); 250 } 251 252 @Rpc(description = "Toggles call waiting feature on or off for specified subscription id.") toggleCallWaitingForSubscription( @pcParametername = "subId") @pcOptional Integer subId, @RpcParameter(name = "enabled") @RpcOptional Boolean enabled)253 public void toggleCallWaitingForSubscription( 254 @RpcParameter(name = "subId") 255 @RpcOptional 256 Integer subId, 257 @RpcParameter(name = "enabled") 258 @RpcOptional 259 Boolean enabled) { 260 // TODO: b/26273478 Enable or Disable the call waiting feature 261 } 262 263 @Rpc(description = "Sends an MMI string to Telecom for processing") telecomHandleMmi( @pcParametername = "dialString") String dialString)264 public void telecomHandleMmi( 265 @RpcParameter(name = "dialString") 266 String dialString) { 267 mTelecomManager.handleMmi(dialString); 268 } 269 270 // TODO: b/20917712 add support to pass arbitrary "Extras" object 271 // for videoCall parameter 272 @Deprecated 273 @Rpc(description = "Calls a phone by resolving a generic URI.") telecomCall( @pcParametername = "uriString") final String uriString, @RpcParameter(name = "videoCall") @RpcOptional @RpcDefault("false") Boolean videoCall)274 public void telecomCall( 275 @RpcParameter(name = "uriString") 276 final String uriString, 277 @RpcParameter(name = "videoCall") 278 @RpcOptional 279 @RpcDefault("false") 280 Boolean videoCall) throws Exception { 281 282 Log.w("Function telecomCall is deprecated; please use a URI-specific call"); 283 284 Uri uri = Uri.parse(uriString); 285 if (uri.getScheme().equals("content")) { 286 telecomCallContentUri(uriString, videoCall); 287 } 288 else { 289 telecomCallNumber(uriString, videoCall); 290 } 291 } 292 293 // TODO: b/20917712 add support to pass arbitrary "Extras" object 294 // for videoCall parameter 295 @Rpc(description = "Calls a phone by resolving a Content-type URI.") telecomCallContentUri( @pcParametername = "uriString") final String uriString, @RpcParameter(name = "videoCall") @RpcOptional @RpcDefault("false") Boolean videoCall)296 public void telecomCallContentUri( 297 @RpcParameter(name = "uriString") 298 final String uriString, 299 @RpcParameter(name = "videoCall") 300 @RpcOptional 301 @RpcDefault("false") 302 Boolean videoCall) 303 throws Exception { 304 Uri uri = Uri.parse(uriString); 305 if (!uri.getScheme().equals("content")) { 306 Log.e("Invalid URI!!"); 307 return; 308 } 309 310 String phoneNumberColumn = ContactsContract.PhoneLookup.NUMBER; 311 String selectWhere = null; 312 if ((FacadeManager.class.cast(mManager)).getSdkLevel() >= 5) { 313 Class<?> contactsContract_Data_class = 314 Class.forName("android.provider.ContactsContract$Data"); 315 Field RAW_CONTACT_ID_field = 316 contactsContract_Data_class.getField("RAW_CONTACT_ID"); 317 selectWhere = RAW_CONTACT_ID_field.get(null).toString() + "=" 318 + uri.getLastPathSegment(); 319 Field CONTENT_URI_field = 320 contactsContract_Data_class.getField("CONTENT_URI"); 321 uri = Uri.parse(CONTENT_URI_field.get(null).toString()); 322 Class<?> ContactsContract_CommonDataKinds_Phone_class = 323 Class.forName("android.provider.ContactsContract$CommonDataKinds$Phone"); 324 Field NUMBER_field = 325 ContactsContract_CommonDataKinds_Phone_class.getField("NUMBER"); 326 phoneNumberColumn = NUMBER_field.get(null).toString(); 327 } 328 ContentResolver resolver = mService.getContentResolver(); 329 Cursor c = resolver.query(uri, new String[] { 330 phoneNumberColumn 331 }, 332 selectWhere, null, null); 333 String number = ""; 334 if (c.moveToFirst()) { 335 number = c.getString(c.getColumnIndexOrThrow(phoneNumberColumn)); 336 } 337 c.close(); 338 telecomCallNumber(number, videoCall); 339 } 340 341 // TODO: b/20917712 add support to pass arbitrary "Extras" object 342 // for videoCall parameter 343 @Rpc(description = "Calls a phone number.") telecomCallNumber( @pcParametername = "number") final String number, @RpcParameter(name = "videoCall") @RpcOptional @RpcDefault("false") Boolean videoCall)344 public void telecomCallNumber( 345 @RpcParameter(name = "number") 346 final String number, 347 @RpcParameter(name = "videoCall") 348 @RpcOptional 349 @RpcDefault("false") 350 Boolean videoCall) 351 throws Exception { 352 telecomCallTelUri("tel:" + URLEncoder.encode(number, "ASCII"), videoCall); 353 } 354 355 // TODO: b/20917712 add support to pass arbitrary "Extras" object 356 // for videoCall parameter 357 @Rpc(description = "Calls a phone by Tel-URI.") telecomCallTelUri( @pcParametername = "uriString") final String uriString, @RpcParameter(name = "videoCall") @RpcOptional @RpcDefault("false") Boolean videoCall)358 public void telecomCallTelUri( 359 @RpcParameter(name = "uriString") 360 final String uriString, 361 @RpcParameter(name = "videoCall") 362 @RpcOptional 363 @RpcDefault("false") 364 Boolean videoCall) throws Exception { 365 if (!uriString.startsWith("tel:")) { 366 Log.w("Invalid tel URI" + uriString); 367 return; 368 } 369 370 Intent intent = new Intent(Intent.ACTION_CALL); 371 intent.setDataAndType(Uri.parse(uriString).normalizeScheme(), null); 372 373 if (videoCall) { 374 Log.d("Placing a bi-directional video call"); 375 intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 376 VideoProfile.STATE_BIDIRECTIONAL); 377 } 378 379 mAndroidFacade.startActivityIntent(intent, false); 380 } 381 382 @Rpc(description = "Calls an Emergency number.") telecomCallEmergencyNumber( @pcParametername = "number") final String number)383 public void telecomCallEmergencyNumber( 384 @RpcParameter(name = "number") 385 final String number) 386 throws Exception { 387 String uriString = "tel:" + URLEncoder.encode(number, "ASCII"); 388 mAndroidFacade.startActivity(Intent.ACTION_CALL_PRIVILEGED, uriString, 389 null, null, null, null, null); 390 } 391 392 @Rpc(description = "Dials a contact/phone number by URI.") telecomDial( @pcParametername = "uri") final String uri)393 public void telecomDial( 394 @RpcParameter(name = "uri") 395 final String uri) 396 throws Exception { 397 mAndroidFacade.startActivity(Intent.ACTION_DIAL, uri, null, null, null, 398 null, null); 399 } 400 401 @Rpc(description = "Dials a phone number.") telecomDialNumber(@pcParametername = "phone number") final String number)402 public void telecomDialNumber(@RpcParameter(name = "phone number") 403 final String number) 404 throws Exception, UnsupportedEncodingException { 405 telecomDial("tel:" + URLEncoder.encode(number, "ASCII")); 406 } 407 } 408