1 /* 2 * Copyright 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.android.bluetooth.hfp; 18 19 import android.annotation.RequiresPermission; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothHeadset; 22 import android.bluetooth.BluetoothSinkAudioPolicy; 23 import android.content.ActivityNotFoundException; 24 import android.content.Intent; 25 import android.media.AudioManager; 26 import android.net.Uri; 27 import android.os.PowerManager; 28 import android.telecom.PhoneAccount; 29 import android.telecom.PhoneAccountHandle; 30 import android.telecom.TelecomManager; 31 import android.telephony.TelephonyManager; 32 import android.util.Log; 33 34 import com.android.bluetooth.telephony.BluetoothInCallService; 35 import com.android.internal.annotations.VisibleForTesting; 36 37 import java.util.List; 38 39 /** 40 * Defines system calls that is used by state machine/service to either send or receive messages 41 * from the Android System. 42 */ 43 class HeadsetSystemInterface { 44 private static final String TAG = HeadsetSystemInterface.class.getSimpleName(); 45 46 private final HeadsetService mHeadsetService; 47 private final AudioManager mAudioManager; 48 private final HeadsetPhoneState mHeadsetPhoneState; 49 private PowerManager.WakeLock mVoiceRecognitionWakeLock; 50 private final TelephonyManager mTelephonyManager; 51 private final TelecomManager mTelecomManager; 52 HeadsetSystemInterface(HeadsetService headsetService)53 HeadsetSystemInterface(HeadsetService headsetService) { 54 if (headsetService == null) { 55 Log.wtf(TAG, "HeadsetService parameter is null"); 56 } 57 mHeadsetService = headsetService; 58 mAudioManager = mHeadsetService.getSystemService(AudioManager.class); 59 PowerManager powerManager = mHeadsetService.getSystemService(PowerManager.class); 60 mVoiceRecognitionWakeLock = 61 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG + ":VoiceRecognition"); 62 mVoiceRecognitionWakeLock.setReferenceCounted(false); 63 mHeadsetPhoneState = new com.android.bluetooth.hfp.HeadsetPhoneState(mHeadsetService); 64 mTelephonyManager = mHeadsetService.getSystemService(TelephonyManager.class); 65 mTelecomManager = mHeadsetService.getSystemService(TelecomManager.class); 66 } 67 getBluetoothInCallServiceInstance()68 private BluetoothInCallService getBluetoothInCallServiceInstance() { 69 return BluetoothInCallService.getInstance(); 70 } 71 72 /** Stop this system interface */ stop()73 public synchronized void stop() { 74 mHeadsetPhoneState.cleanup(); 75 } 76 77 /** 78 * Get audio manager. Most audio manager operations are pass through and therefore are not 79 * individually managed by this class 80 * 81 * @return audio manager for setting audio parameters 82 */ 83 @VisibleForTesting getAudioManager()84 public AudioManager getAudioManager() { 85 return mAudioManager; 86 } 87 88 /** 89 * Get wake lock for voice recognition 90 * 91 * @return wake lock for voice recognition 92 */ 93 @VisibleForTesting getVoiceRecognitionWakeLock()94 public PowerManager.WakeLock getVoiceRecognitionWakeLock() { 95 return mVoiceRecognitionWakeLock; 96 } 97 98 /** 99 * Get HeadsetPhoneState instance to interact with Telephony service 100 * 101 * @return HeadsetPhoneState interface to interact with Telephony service 102 */ 103 @VisibleForTesting getHeadsetPhoneState()104 public HeadsetPhoneState getHeadsetPhoneState() { 105 return mHeadsetPhoneState; 106 } 107 108 /** 109 * Answer the current incoming call in Telecom service 110 * 111 * @param device the Bluetooth device used for answering this call 112 */ 113 @VisibleForTesting 114 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) answerCall(BluetoothDevice device)115 public void answerCall(BluetoothDevice device) { 116 Log.d(TAG, "answerCall"); 117 if (device == null) { 118 Log.w(TAG, "answerCall device is null"); 119 return; 120 } 121 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 122 if (bluetoothInCallService != null) { 123 BluetoothSinkAudioPolicy callAudioPolicy = 124 mHeadsetService.getHfpCallAudioPolicy(device); 125 if (callAudioPolicy == null 126 || callAudioPolicy.getCallEstablishPolicy() 127 != BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) { 128 mHeadsetService.setActiveDevice(device); 129 } 130 bluetoothInCallService.answerCall(); 131 } else { 132 Log.e(TAG, "Handsfree phone proxy null for answering call"); 133 } 134 } 135 136 /** 137 * Hangup the current call, could either be Telecom call or virtual call 138 * 139 * @param device the Bluetooth device used for hanging up this call 140 */ 141 @VisibleForTesting 142 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) hangupCall(BluetoothDevice device)143 public void hangupCall(BluetoothDevice device) { 144 if (device == null) { 145 Log.w(TAG, "hangupCall device is null"); 146 return; 147 } 148 // Close the virtual call if active. Virtual call should be 149 // terminated for CHUP callback event 150 if (mHeadsetService.isVirtualCallStarted()) { 151 mHeadsetService.stopScoUsingVirtualVoiceCall(); 152 } else { 153 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 154 if (bluetoothInCallService != null) { 155 bluetoothInCallService.hangupCall(); 156 } else { 157 Log.e(TAG, "Handsfree phone proxy null for hanging up call"); 158 } 159 } 160 } 161 162 /** 163 * Instructs Telecom to play the specified DTMF tone for the current foreground call 164 * 165 * @param dtmf dtmf code 166 * @param device the Bluetooth device that sent this code 167 */ 168 @VisibleForTesting 169 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) sendDtmf(int dtmf, BluetoothDevice device)170 public boolean sendDtmf(int dtmf, BluetoothDevice device) { 171 if (device == null) { 172 Log.w(TAG, "sendDtmf device is null"); 173 return false; 174 } 175 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 176 if (bluetoothInCallService != null) { 177 return bluetoothInCallService.sendDtmf(dtmf); 178 } else { 179 Log.e(TAG, "Handsfree phone proxy null for sending DTMF"); 180 } 181 return false; 182 } 183 184 /** 185 * Instructs Telecom hold an incoming call 186 * 187 * @param chld index of the call to hold 188 */ 189 @VisibleForTesting 190 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) processChld(int chld)191 public boolean processChld(int chld) { 192 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 193 if (bluetoothInCallService != null) { 194 return bluetoothInCallService.processChld(chld); 195 } else { 196 Log.e(TAG, "Handsfree phone proxy null for sending DTMF"); 197 } 198 return false; 199 } 200 201 /** Check for HD codec for voice call */ 202 @VisibleForTesting isHighDefCallInProgress()203 public boolean isHighDefCallInProgress() { 204 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 205 if (bluetoothInCallService != null) { 206 return bluetoothInCallService.isHighDefCallInProgress(); 207 } else { 208 Log.e(TAG, "Handsfree phone proxy null"); 209 } 210 return false; 211 } 212 213 /** 214 * Get the the alphabetic name of current registered operator. 215 * 216 * @return null on error, empty string if not available 217 */ 218 @VisibleForTesting 219 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) getNetworkOperator()220 public String getNetworkOperator() { 221 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 222 if (bluetoothInCallService == null) { 223 Log.e(TAG, "getNetworkOperator() failed: mBluetoothInCallService is null"); 224 return null; 225 } 226 // Should never return null 227 return bluetoothInCallService.getNetworkOperator(); 228 } 229 230 /** 231 * Get the phone number of this device without incall service 232 * 233 * @return empty if unavailable 234 */ 235 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) getNumberWithoutInCallService()236 private String getNumberWithoutInCallService() { 237 PhoneAccount account = null; 238 String address = ""; 239 240 // Get the label for the default Phone Account. 241 List<PhoneAccountHandle> handles = 242 mTelecomManager.getPhoneAccountsSupportingScheme(PhoneAccount.SCHEME_TEL); 243 while (handles.iterator().hasNext()) { 244 account = mTelecomManager.getPhoneAccount(handles.iterator().next()); 245 break; 246 } 247 248 if (account != null) { 249 Uri addressUri = account.getAddress(); 250 251 if (addressUri != null) { 252 address = addressUri.getSchemeSpecificPart(); 253 } 254 } 255 256 if (address.isEmpty()) { 257 address = mTelephonyManager.getLine1Number(); 258 if (address == null) address = ""; 259 } 260 261 Log.i(TAG, String.format("get phone number -> '%s'", address)); 262 263 return address; 264 } 265 266 /** 267 * Get the phone number of this device 268 * 269 * @return null if unavailable 270 */ 271 @VisibleForTesting 272 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) getSubscriberNumber()273 public String getSubscriberNumber() { 274 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 275 if (bluetoothInCallService == null) { 276 Log.e(TAG, "getSubscriberNumber() failed: mBluetoothInCallService is null"); 277 Log.i(TAG, "Try to get phone number without mBluetoothInCallService."); 278 return getNumberWithoutInCallService(); 279 } 280 return bluetoothInCallService.getSubscriberNumber(); 281 } 282 283 /** 284 * Ask the Telecomm service to list current list of calls through CLCC response {@link 285 * BluetoothHeadset#clccResponse(int, int, int, int, boolean, String, int)} 286 */ 287 @VisibleForTesting 288 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) listCurrentCalls()289 public boolean listCurrentCalls() { 290 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 291 if (bluetoothInCallService == null) { 292 Log.e(TAG, "listCurrentCalls() failed: mBluetoothInCallService is null"); 293 return false; 294 } 295 return bluetoothInCallService.listCurrentCalls(); 296 } 297 298 /** 299 * Request Telecom service to send an update of the current call state to the headset service 300 * through {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int)} 301 */ 302 @VisibleForTesting 303 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) queryPhoneState()304 public void queryPhoneState() { 305 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 306 if (bluetoothInCallService != null) { 307 bluetoothInCallService.queryPhoneState(); 308 } else { 309 Log.e(TAG, "Handsfree phone proxy null for query phone state"); 310 } 311 } 312 313 /** 314 * Check if we are currently in a phone call 315 * 316 * @return True iff we are in a phone call 317 */ 318 @VisibleForTesting isInCall()319 public boolean isInCall() { 320 return ((mHeadsetPhoneState.getNumActiveCall() > 0) 321 || (mHeadsetPhoneState.getNumHeldCall() > 0) 322 || ((mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE) 323 && (mHeadsetPhoneState.getCallState() 324 != HeadsetHalConstants.CALL_STATE_INCOMING))); 325 } 326 327 /** 328 * Check if there is currently an incoming call 329 * 330 * @return True iff there is an incoming call 331 */ 332 @VisibleForTesting isRinging()333 public boolean isRinging() { 334 return mHeadsetPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING; 335 } 336 337 /** 338 * Check if call status is idle 339 * 340 * @return true if call state is neither ringing nor in call 341 */ 342 @VisibleForTesting isCallIdle()343 public boolean isCallIdle() { 344 return !isInCall() && !isRinging(); 345 } 346 347 /** 348 * Activate voice recognition on Android system 349 * 350 * @return true if activation succeeds, caller should wait for {@link 351 * BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} callback that will then trigger 352 * {@link HeadsetService#startVoiceRecognition(BluetoothDevice)}, false if failed to 353 * activate 354 */ 355 @VisibleForTesting activateVoiceRecognition()356 public boolean activateVoiceRecognition() { 357 Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND); 358 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 359 try { 360 mHeadsetService.startActivity(intent); 361 } catch (ActivityNotFoundException e) { 362 Log.e(TAG, "activateVoiceRecognition, failed due to activity not found for " + intent); 363 return false; 364 } 365 return true; 366 } 367 368 /** 369 * Deactivate voice recognition on Android system 370 * 371 * @return true if activation succeeds, caller should wait for {@link 372 * BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} callback that will then trigger 373 * {@link HeadsetService#stopVoiceRecognition(BluetoothDevice)}, false if failed to activate 374 */ 375 @VisibleForTesting deactivateVoiceRecognition()376 public boolean deactivateVoiceRecognition() { 377 // TODO: need a method to deactivate voice recognition on Android 378 return true; 379 } 380 } 381