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