1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.media; 6 7import android.bluetooth.BluetoothAdapter; 8import android.bluetooth.BluetoothManager; 9import android.content.BroadcastReceiver; 10import android.content.ContentResolver; 11import android.content.Context; 12import android.content.Intent; 13import android.content.IntentFilter; 14import android.content.pm.PackageManager; 15import android.database.ContentObserver; 16import android.media.AudioFormat; 17import android.media.AudioManager; 18import android.media.AudioRecord; 19import android.media.AudioTrack; 20import android.media.audiofx.AcousticEchoCanceler; 21import android.os.Build; 22import android.os.Handler; 23import android.os.HandlerThread; 24import android.os.Process; 25import android.provider.Settings; 26import android.util.Log; 27 28import org.chromium.base.CalledByNative; 29import org.chromium.base.JNINamespace; 30 31import java.util.ArrayList; 32import java.util.Arrays; 33import java.util.List; 34 35@JNINamespace("media") 36class AudioManagerAndroid { 37 private static final String TAG = "AudioManagerAndroid"; 38 39 // Set to true to enable debug logs. Avoid in production builds. 40 // NOTE: always check in as false. 41 private static final boolean DEBUG = false; 42 43 /** 44 * NonThreadSafe is a helper class used to help verify that methods of a 45 * class are called from the same thread. 46 * Inspired by class in package com.google.android.apps.chrome.utilities. 47 * Is only utilized when DEBUG is set to true. 48 */ 49 private static class NonThreadSafe { 50 private final Long mThreadId; 51 52 public NonThreadSafe() { 53 if (DEBUG) { 54 mThreadId = Thread.currentThread().getId(); 55 } else { 56 // Avoids "Unread field" issue reported by findbugs. 57 mThreadId = 0L; 58 } 59 } 60 61 /** 62 * Checks if the method is called on the valid thread. 63 * Assigns the current thread if no thread was assigned. 64 */ 65 public boolean calledOnValidThread() { 66 if (DEBUG) { 67 return mThreadId.equals(Thread.currentThread().getId()); 68 } 69 return true; 70 } 71 } 72 73 private static boolean runningOnJellyBeanOrHigher() { 74 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; 75 } 76 77 private static boolean runningOnJellyBeanMR1OrHigher() { 78 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1; 79 } 80 81 private static boolean runningOnJellyBeanMR2OrHigher() { 82 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; 83 } 84 85 /** Simple container for device information. */ 86 private static class AudioDeviceName { 87 private final int mId; 88 private final String mName; 89 90 private AudioDeviceName(int id, String name) { 91 mId = id; 92 mName = name; 93 } 94 95 @CalledByNative("AudioDeviceName") 96 private String id() { return String.valueOf(mId); } 97 98 @CalledByNative("AudioDeviceName") 99 private String name() { return mName; } 100 } 101 102 // List if device models which have been vetted for good quality platform 103 // echo cancellation. 104 // NOTE: only add new devices to this list if manual tests have been 105 // performed where the AEC performance is evaluated using e.g. a WebRTC 106 // audio client such as https://apprtc.appspot.com/?r=<ROOM NAME>. 107 private static final String[] SUPPORTED_AEC_MODELS = new String[] { 108 "GT-I9300", // Galaxy S3 109 "GT-I9500", // Galaxy S4 110 "GT-N7105", // Galaxy Note 2 111 "Nexus 4", // Nexus 4 112 "Nexus 5", // Nexus 5 113 "Nexus 7", // Nexus 7 114 "SM-N9005", // Galaxy Note 3 115 "SM-T310", // Galaxy Tab 3 8.0 (WiFi) 116 }; 117 118 // Supported audio device types. 119 private static final int DEVICE_DEFAULT = -2; 120 private static final int DEVICE_INVALID = -1; 121 private static final int DEVICE_SPEAKERPHONE = 0; 122 private static final int DEVICE_WIRED_HEADSET = 1; 123 private static final int DEVICE_EARPIECE = 2; 124 private static final int DEVICE_BLUETOOTH_HEADSET = 3; 125 private static final int DEVICE_COUNT = 4; 126 127 // Maps audio device types to string values. This map must be in sync 128 // with the device types above. 129 // TODO(henrika): add support for proper detection of device names and 130 // localize the name strings by using resource strings. 131 // See http://crbug.com/333208 for details. 132 private static final String[] DEVICE_NAMES = new String[] { 133 "Speakerphone", 134 "Wired headset", // With or without microphone. 135 "Headset earpiece", // Only available on mobile phones. 136 "Bluetooth headset", // Requires BLUETOOTH permission. 137 }; 138 139 // List of valid device types. 140 private static final Integer[] VALID_DEVICES = new Integer[] { 141 DEVICE_SPEAKERPHONE, 142 DEVICE_WIRED_HEADSET, 143 DEVICE_EARPIECE, 144 DEVICE_BLUETOOTH_HEADSET, 145 }; 146 147 // Bluetooth audio SCO states. Example of valid state sequence: 148 // SCO_INVALID -> SCO_TURNING_ON -> SCO_ON -> SCO_TURNING_OFF -> SCO_OFF. 149 private static final int STATE_BLUETOOTH_SCO_INVALID = -1; 150 private static final int STATE_BLUETOOTH_SCO_OFF = 0; 151 private static final int STATE_BLUETOOTH_SCO_ON = 1; 152 private static final int STATE_BLUETOOTH_SCO_TURNING_ON = 2; 153 private static final int STATE_BLUETOOTH_SCO_TURNING_OFF = 3; 154 155 // Use 44.1kHz as the default sampling rate. 156 private static final int DEFAULT_SAMPLING_RATE = 44100; 157 // Randomly picked up frame size which is close to return value on N4. 158 // Return this value when getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER) 159 // fails. 160 private static final int DEFAULT_FRAME_PER_BUFFER = 256; 161 162 private final AudioManager mAudioManager; 163 private final Context mContext; 164 private final long mNativeAudioManagerAndroid; 165 166 // Enabled during initialization if MODIFY_AUDIO_SETTINGS permission is 167 // granted. Required to shift system-wide audio settings. 168 private boolean mHasModifyAudioSettingsPermission = false; 169 170 // Enabled during initialization if RECORD_AUDIO permission is granted. 171 private boolean mHasRecordAudioPermission = false; 172 173 // Enabled during initialization if BLUETOOTH permission is granted. 174 private boolean mHasBluetoothPermission = false; 175 176 private int mSavedAudioMode = AudioManager.MODE_INVALID; 177 178 // Stores the audio states related to Bluetooth SCO audio, where some 179 // states are needed to keep track of intermediate states while the SCO 180 // channel is enabled or disabled (switching state can take a few seconds). 181 private int mBluetoothScoState = STATE_BLUETOOTH_SCO_INVALID; 182 183 private boolean mIsInitialized = false; 184 private boolean mSavedIsSpeakerphoneOn; 185 private boolean mSavedIsMicrophoneMute; 186 187 // Id of the requested audio device. Can only be modified by 188 // call to setDevice(). 189 private int mRequestedAudioDevice = DEVICE_INVALID; 190 191 // This class should be created, initialized and closed on the audio thread 192 // in the audio manager. We use |mNonThreadSafe| to ensure that this is 193 // the case. Only active when |DEBUG| is set to true. 194 private final NonThreadSafe mNonThreadSafe = new NonThreadSafe(); 195 196 // Lock to protect |mAudioDevices| and |mRequestedAudioDevice| which can 197 // be accessed from the main thread and the audio manager thread. 198 private final Object mLock = new Object(); 199 200 // Contains a list of currently available audio devices. 201 private boolean[] mAudioDevices = new boolean[DEVICE_COUNT]; 202 203 private final ContentResolver mContentResolver; 204 private ContentObserver mSettingsObserver = null; 205 private HandlerThread mSettingsObserverThread = null; 206 private int mCurrentVolume; 207 208 // Broadcast receiver for wired headset intent broadcasts. 209 private BroadcastReceiver mWiredHeadsetReceiver; 210 211 // Broadcast receiver for Bluetooth headset intent broadcasts. 212 // Utilized to detect changes in Bluetooth headset availability. 213 private BroadcastReceiver mBluetoothHeadsetReceiver; 214 215 // Broadcast receiver for Bluetooth SCO broadcasts. 216 // Utilized to detect if BT SCO streaming is on or off. 217 private BroadcastReceiver mBluetoothScoReceiver; 218 219 /** Construction */ 220 @CalledByNative 221 private static AudioManagerAndroid createAudioManagerAndroid( 222 Context context, 223 long nativeAudioManagerAndroid) { 224 return new AudioManagerAndroid(context, nativeAudioManagerAndroid); 225 } 226 227 private AudioManagerAndroid(Context context, long nativeAudioManagerAndroid) { 228 mContext = context; 229 mNativeAudioManagerAndroid = nativeAudioManagerAndroid; 230 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 231 mContentResolver = mContext.getContentResolver(); 232 } 233 234 /** 235 * Saves the initial speakerphone and microphone state. 236 * Populates the list of available audio devices and registers receivers 237 * for broadcast intents related to wired headset and Bluetooth devices. 238 */ 239 @CalledByNative 240 private void init() { 241 checkIfCalledOnValidThread(); 242 if (DEBUG) logd("init"); 243 if (DEBUG) logDeviceInfo(); 244 if (mIsInitialized) 245 return; 246 247 // Check if process has MODIFY_AUDIO_SETTINGS and RECORD_AUDIO 248 // permissions. Both are required for full functionality. 249 mHasModifyAudioSettingsPermission = hasPermission( 250 android.Manifest.permission.MODIFY_AUDIO_SETTINGS); 251 if (DEBUG && !mHasModifyAudioSettingsPermission) { 252 logd("MODIFY_AUDIO_SETTINGS permission is missing"); 253 } 254 mHasRecordAudioPermission = hasPermission( 255 android.Manifest.permission.RECORD_AUDIO); 256 if (DEBUG && !mHasRecordAudioPermission) { 257 logd("RECORD_AUDIO permission is missing"); 258 } 259 260 // Initialize audio device list with things we know is always available. 261 mAudioDevices[DEVICE_EARPIECE] = hasEarpiece(); 262 mAudioDevices[DEVICE_WIRED_HEADSET] = hasWiredHeadset(); 263 mAudioDevices[DEVICE_SPEAKERPHONE] = true; 264 265 // Register receivers for broadcast intents related to Bluetooth device 266 // and Bluetooth SCO notifications. Requires BLUETOOTH permission. 267 registerBluetoothIntentsIfNeeded(); 268 269 // Register receiver for broadcast intents related to adding/ 270 // removing a wired headset (Intent.ACTION_HEADSET_PLUG). 271 registerForWiredHeadsetIntentBroadcast(); 272 273 mIsInitialized = true; 274 275 if (DEBUG) reportUpdate(); 276 } 277 278 /** 279 * Unregister all previously registered intent receivers and restore 280 * the stored state (stored in {@link #init()}). 281 */ 282 @CalledByNative 283 private void close() { 284 checkIfCalledOnValidThread(); 285 if (DEBUG) logd("close"); 286 if (!mIsInitialized) 287 return; 288 289 stopObservingVolumeChanges(); 290 unregisterForWiredHeadsetIntentBroadcast(); 291 unregisterBluetoothIntentsIfNeeded(); 292 293 mIsInitialized = false; 294 } 295 296 /** 297 * Saves current audio mode and sets audio mode to MODE_IN_COMMUNICATION 298 * if input parameter is true. Restores saved audio mode if input parameter 299 * is false. 300 * Required permission: android.Manifest.permission.MODIFY_AUDIO_SETTINGS. 301 */ 302 @CalledByNative 303 private void setCommunicationAudioModeOn(boolean on) { 304 if (DEBUG) logd("setCommunicationAudioModeOn(" + on + ")"); 305 306 // The MODIFY_AUDIO_SETTINGS permission is required to allow an 307 // application to modify global audio settings. 308 if (!mHasModifyAudioSettingsPermission) { 309 Log.w(TAG, "MODIFY_AUDIO_SETTINGS is missing => client will run " + 310 "with reduced functionality"); 311 return; 312 } 313 314 if (on) { 315 if (mSavedAudioMode != AudioManager.MODE_INVALID) { 316 throw new IllegalStateException("Audio mode has already been set"); 317 } 318 319 // Store the current audio mode the first time we try to 320 // switch to communication mode. 321 try { 322 mSavedAudioMode = mAudioManager.getMode(); 323 } catch (SecurityException e) { 324 logDeviceInfo(); 325 throw e; 326 327 } 328 329 // Store microphone mute state and speakerphone state so it can 330 // be restored when closing. 331 mSavedIsSpeakerphoneOn = mAudioManager.isSpeakerphoneOn(); 332 mSavedIsMicrophoneMute = mAudioManager.isMicrophoneMute(); 333 334 try { 335 mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); 336 } catch (SecurityException e) { 337 logDeviceInfo(); 338 throw e; 339 } 340 341 // Start observing volume changes to detect when the 342 // voice/communication stream volume is at its lowest level. 343 // It is only possible to pull down the volume slider to about 20% 344 // of the absolute minimum (slider at far left) in communication 345 // mode but we want to be able to mute it completely. 346 startObservingVolumeChanges(); 347 348 } else { 349 if (mSavedAudioMode == AudioManager.MODE_INVALID) { 350 throw new IllegalStateException("Audio mode has not yet been set"); 351 } 352 353 stopObservingVolumeChanges(); 354 355 // Restore previously stored audio states. 356 setMicrophoneMute(mSavedIsMicrophoneMute); 357 setSpeakerphoneOn(mSavedIsSpeakerphoneOn); 358 359 // Restore the mode that was used before we switched to 360 // communication mode. 361 try { 362 mAudioManager.setMode(mSavedAudioMode); 363 } catch (SecurityException e) { 364 logDeviceInfo(); 365 throw e; 366 } 367 mSavedAudioMode = AudioManager.MODE_INVALID; 368 } 369 } 370 371 /** 372 * Activates, i.e., starts routing audio to, the specified audio device. 373 * 374 * @param deviceId Unique device ID (integer converted to string) 375 * representing the selected device. This string is empty if the so-called 376 * default device is requested. 377 * Required permissions: android.Manifest.permission.MODIFY_AUDIO_SETTINGS 378 * and android.Manifest.permission.RECORD_AUDIO. 379 */ 380 @CalledByNative 381 private boolean setDevice(String deviceId) { 382 if (DEBUG) logd("setDevice: " + deviceId); 383 if (!mIsInitialized) 384 return false; 385 if (!mHasModifyAudioSettingsPermission || !mHasRecordAudioPermission) { 386 Log.w(TAG, "Requires MODIFY_AUDIO_SETTINGS and RECORD_AUDIO"); 387 Log.w(TAG, "Selected device will not be available for recording"); 388 return false; 389 } 390 391 int intDeviceId = deviceId.isEmpty() ? 392 DEVICE_DEFAULT : Integer.parseInt(deviceId); 393 394 if (intDeviceId == DEVICE_DEFAULT) { 395 boolean devices[] = null; 396 synchronized (mLock) { 397 devices = mAudioDevices.clone(); 398 mRequestedAudioDevice = DEVICE_DEFAULT; 399 } 400 int defaultDevice = selectDefaultDevice(devices); 401 setAudioDevice(defaultDevice); 402 return true; 403 } 404 405 // A non-default device is specified. Verify that it is valid 406 // device, and if so, start using it. 407 List<Integer> validIds = Arrays.asList(VALID_DEVICES); 408 if (!validIds.contains(intDeviceId) || !mAudioDevices[intDeviceId]) { 409 return false; 410 } 411 synchronized (mLock) { 412 mRequestedAudioDevice = intDeviceId; 413 } 414 setAudioDevice(intDeviceId); 415 return true; 416 } 417 418 /** 419 * @return the current list of available audio devices. 420 * Note that this call does not trigger any update of the list of devices, 421 * it only copies the current state in to the output array. 422 * Required permissions: android.Manifest.permission.MODIFY_AUDIO_SETTINGS 423 * and android.Manifest.permission.RECORD_AUDIO. 424 */ 425 @CalledByNative 426 private AudioDeviceName[] getAudioInputDeviceNames() { 427 if (DEBUG) logd("getAudioInputDeviceNames"); 428 if (!mIsInitialized) 429 return null; 430 if (!mHasModifyAudioSettingsPermission || !mHasRecordAudioPermission) { 431 Log.w(TAG, "Requires MODIFY_AUDIO_SETTINGS and RECORD_AUDIO"); 432 Log.w(TAG, "No audio device will be available for recording"); 433 return null; 434 } 435 436 boolean devices[] = null; 437 synchronized (mLock) { 438 devices = mAudioDevices.clone(); 439 } 440 List<String> list = new ArrayList<String>(); 441 AudioDeviceName[] array = 442 new AudioDeviceName[getNumOfAudioDevices(devices)]; 443 int i = 0; 444 for (int id = 0; id < DEVICE_COUNT; ++id) { 445 if (devices[id]) { 446 array[i] = new AudioDeviceName(id, DEVICE_NAMES[id]); 447 list.add(DEVICE_NAMES[id]); 448 i++; 449 } 450 } 451 if (DEBUG) logd("getAudioInputDeviceNames: " + list); 452 return array; 453 } 454 455 @CalledByNative 456 private int getNativeOutputSampleRate() { 457 if (runningOnJellyBeanMR1OrHigher()) { 458 String sampleRateString = mAudioManager.getProperty( 459 AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 460 return (sampleRateString == null ? 461 DEFAULT_SAMPLING_RATE : Integer.parseInt(sampleRateString)); 462 } else { 463 return DEFAULT_SAMPLING_RATE; 464 } 465 } 466 467 /** 468 * Returns the minimum frame size required for audio input. 469 * 470 * @param sampleRate sampling rate 471 * @param channels number of channels 472 */ 473 @CalledByNative 474 private static int getMinInputFrameSize(int sampleRate, int channels) { 475 int channelConfig; 476 if (channels == 1) { 477 channelConfig = AudioFormat.CHANNEL_IN_MONO; 478 } else if (channels == 2) { 479 channelConfig = AudioFormat.CHANNEL_IN_STEREO; 480 } else { 481 return -1; 482 } 483 return AudioRecord.getMinBufferSize( 484 sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels; 485 } 486 487 /** 488 * Returns the minimum frame size required for audio output. 489 * 490 * @param sampleRate sampling rate 491 * @param channels number of channels 492 */ 493 @CalledByNative 494 private static int getMinOutputFrameSize(int sampleRate, int channels) { 495 int channelConfig; 496 if (channels == 1) { 497 channelConfig = AudioFormat.CHANNEL_OUT_MONO; 498 } else if (channels == 2) { 499 channelConfig = AudioFormat.CHANNEL_OUT_STEREO; 500 } else { 501 return -1; 502 } 503 return AudioTrack.getMinBufferSize( 504 sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels; 505 } 506 507 @CalledByNative 508 private boolean isAudioLowLatencySupported() { 509 return mContext.getPackageManager().hasSystemFeature( 510 PackageManager.FEATURE_AUDIO_LOW_LATENCY); 511 } 512 513 @CalledByNative 514 private int getAudioLowLatencyOutputFrameSize() { 515 String framesPerBuffer = 516 mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 517 return (framesPerBuffer == null ? 518 DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer)); 519 } 520 521 @CalledByNative 522 private static boolean shouldUseAcousticEchoCanceler() { 523 // AcousticEchoCanceler was added in API level 16 (Jelly Bean). 524 if (!runningOnJellyBeanOrHigher()) { 525 return false; 526 } 527 528 // Verify that this device is among the supported/tested models. 529 List<String> supportedModels = Arrays.asList(SUPPORTED_AEC_MODELS); 530 if (!supportedModels.contains(Build.MODEL)) { 531 return false; 532 } 533 if (DEBUG && AcousticEchoCanceler.isAvailable()) { 534 logd("Approved for use of hardware acoustic echo canceler."); 535 } 536 537 // As a final check, verify that the device supports acoustic echo 538 // cancellation. 539 return AcousticEchoCanceler.isAvailable(); 540 } 541 542 /** 543 * Helper method for debugging purposes. Ensures that method is 544 * called on same thread as this object was created on. 545 */ 546 private void checkIfCalledOnValidThread() { 547 if (DEBUG && !mNonThreadSafe.calledOnValidThread()) { 548 throw new IllegalStateException("Method is not called on valid thread"); 549 } 550 } 551 552 /** 553 * Register for BT intents if we have the BLUETOOTH permission. 554 * Also extends the list of available devices with a BT device if one exists. 555 */ 556 private void registerBluetoothIntentsIfNeeded() { 557 // Check if this process has the BLUETOOTH permission or not. 558 mHasBluetoothPermission = hasPermission( 559 android.Manifest.permission.BLUETOOTH); 560 561 // Add a Bluetooth headset to the list of available devices if a BT 562 // headset is detected and if we have the BLUETOOTH permission. 563 // We must do this initial check using a dedicated method since the 564 // broadcasted intent BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED 565 // is not sticky and will only be received if a BT headset is connected 566 // after this method has been called. 567 if (!mHasBluetoothPermission) { 568 Log.w(TAG, "Requires BLUETOOTH permission"); 569 return; 570 } 571 mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = hasBluetoothHeadset(); 572 573 // Register receivers for broadcast intents related to changes in 574 // Bluetooth headset availability and usage of the SCO channel. 575 registerForBluetoothHeadsetIntentBroadcast(); 576 registerForBluetoothScoIntentBroadcast(); 577 } 578 579 /** Unregister for BT intents if a registration has been made. */ 580 private void unregisterBluetoothIntentsIfNeeded() { 581 if (mHasBluetoothPermission) { 582 mAudioManager.stopBluetoothSco(); 583 unregisterForBluetoothHeadsetIntentBroadcast(); 584 unregisterForBluetoothScoIntentBroadcast(); 585 } 586 } 587 588 /** Sets the speaker phone mode. */ 589 private void setSpeakerphoneOn(boolean on) { 590 boolean wasOn = mAudioManager.isSpeakerphoneOn(); 591 if (wasOn == on) { 592 return; 593 } 594 mAudioManager.setSpeakerphoneOn(on); 595 } 596 597 /** Sets the microphone mute state. */ 598 private void setMicrophoneMute(boolean on) { 599 boolean wasMuted = mAudioManager.isMicrophoneMute(); 600 if (wasMuted == on) { 601 return; 602 } 603 mAudioManager.setMicrophoneMute(on); 604 } 605 606 /** Gets the current microphone mute state. */ 607 private boolean isMicrophoneMute() { 608 return mAudioManager.isMicrophoneMute(); 609 } 610 611 /** Gets the current earpiece state. */ 612 private boolean hasEarpiece() { 613 return mContext.getPackageManager().hasSystemFeature( 614 PackageManager.FEATURE_TELEPHONY); 615 } 616 617 /** 618 * Checks whether a wired headset is connected or not. 619 * This is not a valid indication that audio playback is actually over 620 * the wired headset as audio routing depends on other conditions. We 621 * only use it as an early indicator (during initialization) of an attached 622 * wired headset. 623 */ 624 @Deprecated 625 private boolean hasWiredHeadset() { 626 return mAudioManager.isWiredHeadsetOn(); 627 } 628 629 /** Checks if the process has as specified permission or not. */ 630 private boolean hasPermission(String permission) { 631 return mContext.checkPermission( 632 permission, 633 Process.myPid(), 634 Process.myUid()) == PackageManager.PERMISSION_GRANTED; 635 } 636 637 /** 638 * Gets the current Bluetooth headset state. 639 * android.bluetooth.BluetoothAdapter.getProfileConnectionState() requires 640 * the BLUETOOTH permission. 641 */ 642 private boolean hasBluetoothHeadset() { 643 if (!mHasBluetoothPermission) { 644 Log.w(TAG, "hasBluetoothHeadset() requires BLUETOOTH permission"); 645 return false; 646 } 647 648 // To get a BluetoothAdapter representing the local Bluetooth adapter, 649 // when running on JELLY_BEAN_MR1 (4.2) and below, call the static 650 // getDefaultAdapter() method; when running on JELLY_BEAN_MR2 (4.3) and 651 // higher, retrieve it through getSystemService(String) with 652 // BLUETOOTH_SERVICE. 653 BluetoothAdapter btAdapter = null; 654 if (runningOnJellyBeanMR2OrHigher()) { 655 // Use BluetoothManager to get the BluetoothAdapter for 656 // Android 4.3 and above. 657 BluetoothManager btManager = 658 (BluetoothManager) mContext.getSystemService( 659 Context.BLUETOOTH_SERVICE); 660 btAdapter = btManager.getAdapter(); 661 } else { 662 // Use static method for Android 4.2 and below to get the 663 // BluetoothAdapter. 664 btAdapter = BluetoothAdapter.getDefaultAdapter(); 665 } 666 667 if (btAdapter == null) { 668 // Bluetooth not supported on this platform. 669 return false; 670 } 671 672 int profileConnectionState; 673 profileConnectionState = btAdapter.getProfileConnectionState( 674 android.bluetooth.BluetoothProfile.HEADSET); 675 676 // Ensure that Bluetooth is enabled and that a device which supports the 677 // headset and handsfree profile is connected. 678 // TODO(henrika): it is possible that btAdapter.isEnabled() is 679 // redundant. It might be sufficient to only check the profile state. 680 return btAdapter.isEnabled() && profileConnectionState == 681 android.bluetooth.BluetoothProfile.STATE_CONNECTED; 682 } 683 684 /** 685 * Registers receiver for the broadcasted intent when a wired headset is 686 * plugged in or unplugged. The received intent will have an extra 687 * 'state' value where 0 means unplugged, and 1 means plugged. 688 */ 689 private void registerForWiredHeadsetIntentBroadcast() { 690 IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); 691 692 /** Receiver which handles changes in wired headset availability. */ 693 mWiredHeadsetReceiver = new BroadcastReceiver() { 694 private static final int STATE_UNPLUGGED = 0; 695 private static final int STATE_PLUGGED = 1; 696 private static final int HAS_NO_MIC = 0; 697 private static final int HAS_MIC = 1; 698 699 @Override 700 public void onReceive(Context context, Intent intent) { 701 int state = intent.getIntExtra("state", STATE_UNPLUGGED); 702 if (DEBUG) { 703 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); 704 String name = intent.getStringExtra("name"); 705 logd("BroadcastReceiver.onReceive: a=" + intent.getAction() + 706 ", s=" + state + 707 ", m=" + microphone + 708 ", n=" + name + 709 ", sb=" + isInitialStickyBroadcast()); 710 } 711 switch (state) { 712 case STATE_UNPLUGGED: 713 synchronized (mLock) { 714 // Wired headset and earpiece are mutually exclusive. 715 mAudioDevices[DEVICE_WIRED_HEADSET] = false; 716 if (hasEarpiece()) { 717 mAudioDevices[DEVICE_EARPIECE] = true; 718 } 719 } 720 break; 721 case STATE_PLUGGED: 722 synchronized (mLock) { 723 // Wired headset and earpiece are mutually exclusive. 724 mAudioDevices[DEVICE_WIRED_HEADSET] = true; 725 mAudioDevices[DEVICE_EARPIECE] = false; 726 } 727 break; 728 default: 729 loge("Invalid state"); 730 break; 731 } 732 733 // Update the existing device selection, but only if a specific 734 // device has already been selected explicitly. 735 if (deviceHasBeenRequested()) { 736 updateDeviceActivation(); 737 } else if (DEBUG) { 738 reportUpdate(); 739 } 740 } 741 }; 742 743 // Note: the intent we register for here is sticky, so it'll tell us 744 // immediately what the last action was (plugged or unplugged). 745 // It will enable us to set the speakerphone correctly. 746 mContext.registerReceiver(mWiredHeadsetReceiver, filter); 747 } 748 749 /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */ 750 private void unregisterForWiredHeadsetIntentBroadcast() { 751 mContext.unregisterReceiver(mWiredHeadsetReceiver); 752 mWiredHeadsetReceiver = null; 753 } 754 755 /** 756 * Registers receiver for the broadcasted intent related to BT headset 757 * availability or a change in connection state of the local Bluetooth 758 * adapter. Example: triggers when the BT device is turned on or off. 759 * BLUETOOTH permission is required to receive this one. 760 */ 761 private void registerForBluetoothHeadsetIntentBroadcast() { 762 IntentFilter filter = new IntentFilter( 763 android.bluetooth.BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 764 765 /** Receiver which handles changes in BT headset availability. */ 766 mBluetoothHeadsetReceiver = new BroadcastReceiver() { 767 @Override 768 public void onReceive(Context context, Intent intent) { 769 // A change in connection state of the Headset profile has 770 // been detected, e.g. BT headset has been connected or 771 // disconnected. This broadcast is *not* sticky. 772 int profileState = intent.getIntExtra( 773 android.bluetooth.BluetoothHeadset.EXTRA_STATE, 774 android.bluetooth.BluetoothHeadset.STATE_DISCONNECTED); 775 if (DEBUG) { 776 logd("BroadcastReceiver.onReceive: a=" + intent.getAction() + 777 ", s=" + profileState + 778 ", sb=" + isInitialStickyBroadcast()); 779 } 780 781 switch (profileState) { 782 case android.bluetooth.BluetoothProfile.STATE_DISCONNECTED: 783 // We do not have to explicitly call stopBluetoothSco() 784 // since BT SCO will be disconnected automatically when 785 // the BT headset is disabled. 786 synchronized (mLock) { 787 // Remove the BT device from the list of devices. 788 mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = false; 789 } 790 break; 791 case android.bluetooth.BluetoothProfile.STATE_CONNECTED: 792 synchronized (mLock) { 793 // Add the BT device to the list of devices. 794 mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = true; 795 } 796 break; 797 case android.bluetooth.BluetoothProfile.STATE_CONNECTING: 798 // Bluetooth service is switching from off to on. 799 break; 800 case android.bluetooth.BluetoothProfile.STATE_DISCONNECTING: 801 // Bluetooth service is switching from on to off. 802 break; 803 default: 804 loge("Invalid state"); 805 break; 806 } 807 808 // Update the existing device selection, but only if a specific 809 // device has already been selected explicitly. 810 if (deviceHasBeenRequested()) { 811 updateDeviceActivation(); 812 } else if (DEBUG) { 813 reportUpdate(); 814 } 815 } 816 }; 817 818 mContext.registerReceiver(mBluetoothHeadsetReceiver, filter); 819 } 820 821 private void unregisterForBluetoothHeadsetIntentBroadcast() { 822 mContext.unregisterReceiver(mBluetoothHeadsetReceiver); 823 mBluetoothHeadsetReceiver = null; 824 } 825 826 /** 827 * Registers receiver for the broadcasted intent related the existence 828 * of a BT SCO channel. Indicates if BT SCO streaming is on or off. 829 */ 830 private void registerForBluetoothScoIntentBroadcast() { 831 IntentFilter filter = new IntentFilter( 832 AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); 833 834 /** BroadcastReceiver implementation which handles changes in BT SCO. */ 835 mBluetoothScoReceiver = new BroadcastReceiver() { 836 @Override 837 public void onReceive(Context context, Intent intent) { 838 int state = intent.getIntExtra( 839 AudioManager.EXTRA_SCO_AUDIO_STATE, 840 AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 841 if (DEBUG) { 842 logd("BroadcastReceiver.onReceive: a=" + intent.getAction() + 843 ", s=" + state + 844 ", sb=" + isInitialStickyBroadcast()); 845 } 846 847 switch (state) { 848 case AudioManager.SCO_AUDIO_STATE_CONNECTED: 849 mBluetoothScoState = STATE_BLUETOOTH_SCO_ON; 850 break; 851 case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: 852 mBluetoothScoState = STATE_BLUETOOTH_SCO_OFF; 853 break; 854 case AudioManager.SCO_AUDIO_STATE_CONNECTING: 855 // do nothing 856 break; 857 default: 858 loge("Invalid state"); 859 } 860 if (DEBUG) { 861 reportUpdate(); 862 } 863 } 864 }; 865 866 mContext.registerReceiver(mBluetoothScoReceiver, filter); 867 } 868 869 private void unregisterForBluetoothScoIntentBroadcast() { 870 mContext.unregisterReceiver(mBluetoothScoReceiver); 871 mBluetoothScoReceiver = null; 872 } 873 874 /** Enables BT audio using the SCO audio channel. */ 875 private void startBluetoothSco() { 876 if (!mHasBluetoothPermission) { 877 return; 878 } 879 if (mBluetoothScoState == STATE_BLUETOOTH_SCO_ON || 880 mBluetoothScoState == STATE_BLUETOOTH_SCO_TURNING_ON) { 881 // Unable to turn on BT in this state. 882 return; 883 } 884 885 // Check if audio is already routed to BT SCO; if so, just update 886 // states but don't try to enable it again. 887 if (mAudioManager.isBluetoothScoOn()) { 888 mBluetoothScoState = STATE_BLUETOOTH_SCO_ON; 889 return; 890 } 891 892 if (DEBUG) logd("startBluetoothSco: turning BT SCO on..."); 893 mBluetoothScoState = STATE_BLUETOOTH_SCO_TURNING_ON; 894 mAudioManager.startBluetoothSco(); 895 } 896 897 /** Disables BT audio using the SCO audio channel. */ 898 private void stopBluetoothSco() { 899 if (!mHasBluetoothPermission) { 900 return; 901 } 902 if (mBluetoothScoState != STATE_BLUETOOTH_SCO_ON && 903 mBluetoothScoState != STATE_BLUETOOTH_SCO_TURNING_ON) { 904 // No need to turn off BT in this state. 905 return; 906 } 907 if (!mAudioManager.isBluetoothScoOn()) { 908 // TODO(henrika): can we do anything else than logging here? 909 loge("Unable to stop BT SCO since it is already disabled"); 910 return; 911 } 912 913 if (DEBUG) logd("stopBluetoothSco: turning BT SCO off..."); 914 mBluetoothScoState = STATE_BLUETOOTH_SCO_TURNING_OFF; 915 mAudioManager.stopBluetoothSco(); 916 } 917 918 /** 919 * Changes selection of the currently active audio device. 920 * 921 * @param device Specifies the selected audio device. 922 */ 923 private void setAudioDevice(int device) { 924 if (DEBUG) logd("setAudioDevice(device=" + device + ")"); 925 926 // Ensure that the Bluetooth SCO audio channel is always disabled 927 // unless the BT headset device is selected. 928 if (device == DEVICE_BLUETOOTH_HEADSET) { 929 startBluetoothSco(); 930 } else { 931 stopBluetoothSco(); 932 } 933 934 switch (device) { 935 case DEVICE_BLUETOOTH_HEADSET: 936 break; 937 case DEVICE_SPEAKERPHONE: 938 setSpeakerphoneOn(true); 939 break; 940 case DEVICE_WIRED_HEADSET: 941 setSpeakerphoneOn(false); 942 break; 943 case DEVICE_EARPIECE: 944 setSpeakerphoneOn(false); 945 break; 946 default: 947 loge("Invalid audio device selection"); 948 break; 949 } 950 reportUpdate(); 951 } 952 953 /** 954 * Use a special selection scheme if the default device is selected. 955 * The "most unique" device will be selected; Wired headset first, 956 * then Bluetooth and last the speaker phone. 957 */ 958 private static int selectDefaultDevice(boolean[] devices) { 959 if (devices[DEVICE_WIRED_HEADSET]) { 960 return DEVICE_WIRED_HEADSET; 961 } else if (devices[DEVICE_BLUETOOTH_HEADSET]) { 962 // TODO(henrika): possibly need improvements here if we are 963 // in a state where Bluetooth is turning off. 964 return DEVICE_BLUETOOTH_HEADSET; 965 } 966 return DEVICE_SPEAKERPHONE; 967 } 968 969 /** Returns true if setDevice() has been called with a valid device id. */ 970 private boolean deviceHasBeenRequested() { 971 synchronized (mLock) { 972 return (mRequestedAudioDevice != DEVICE_INVALID); 973 } 974 } 975 976 /** 977 * Updates the active device given the current list of devices and 978 * information about if a specific device has been selected or if 979 * the default device is selected. 980 */ 981 private void updateDeviceActivation() { 982 boolean devices[] = null; 983 int requested = DEVICE_INVALID; 984 synchronized (mLock) { 985 requested = mRequestedAudioDevice; 986 devices = mAudioDevices.clone(); 987 } 988 if (requested == DEVICE_INVALID) { 989 loge("Unable to activate device since no device is selected"); 990 return; 991 } 992 993 // Update default device if it has been selected explicitly, or 994 // the selected device has been removed from the list. 995 if (requested == DEVICE_DEFAULT || !devices[requested]) { 996 // Get default device given current list and activate the device. 997 int defaultDevice = selectDefaultDevice(devices); 998 setAudioDevice(defaultDevice); 999 } else { 1000 // Activate the selected device since we know that it exists in 1001 // the list. 1002 setAudioDevice(requested); 1003 } 1004 } 1005 1006 /** Returns number of available devices */ 1007 private static int getNumOfAudioDevices(boolean[] devices) { 1008 int count = 0; 1009 for (int i = 0; i < DEVICE_COUNT; ++i) { 1010 if (devices[i]) 1011 ++count; 1012 } 1013 return count; 1014 } 1015 1016 /** 1017 * For now, just log the state change but the idea is that we should 1018 * notify a registered state change listener (if any) that there has 1019 * been a change in the state. 1020 * TODO(henrika): add support for state change listener. 1021 */ 1022 private void reportUpdate() { 1023 synchronized (mLock) { 1024 List<String> devices = new ArrayList<String>(); 1025 for (int i = 0; i < DEVICE_COUNT; ++i) { 1026 if (mAudioDevices[i]) 1027 devices.add(DEVICE_NAMES[i]); 1028 } 1029 if (DEBUG) { 1030 logd("reportUpdate: requested=" + mRequestedAudioDevice + 1031 ", btSco=" + mBluetoothScoState + 1032 ", devices=" + devices); 1033 } 1034 } 1035 } 1036 1037 /** Information about the current build, taken from system properties. */ 1038 private void logDeviceInfo() { 1039 logd("Android SDK: " + Build.VERSION.SDK_INT + ", " + 1040 "Release: " + Build.VERSION.RELEASE + ", " + 1041 "Brand: " + Build.BRAND + ", " + 1042 "Device: " + Build.DEVICE + ", " + 1043 "Id: " + Build.ID + ", " + 1044 "Hardware: " + Build.HARDWARE + ", " + 1045 "Manufacturer: " + Build.MANUFACTURER + ", " + 1046 "Model: " + Build.MODEL + ", " + 1047 "Product: " + Build.PRODUCT); 1048 } 1049 1050 /** Trivial helper method for debug logging */ 1051 private static void logd(String msg) { 1052 Log.d(TAG, msg); 1053 } 1054 1055 /** Trivial helper method for error logging */ 1056 private static void loge(String msg) { 1057 Log.e(TAG, msg); 1058 } 1059 1060 /** Start thread which observes volume changes on the voice stream. */ 1061 private void startObservingVolumeChanges() { 1062 if (DEBUG) logd("startObservingVolumeChanges"); 1063 if (mSettingsObserverThread != null) 1064 return; 1065 mSettingsObserverThread = new HandlerThread("SettingsObserver"); 1066 mSettingsObserverThread.start(); 1067 1068 mSettingsObserver = new ContentObserver( 1069 new Handler(mSettingsObserverThread.getLooper())) { 1070 1071 @Override 1072 public void onChange(boolean selfChange) { 1073 if (DEBUG) logd("SettingsObserver.onChange: " + selfChange); 1074 super.onChange(selfChange); 1075 1076 // Ensure that the observer is activated during communication mode. 1077 if (mAudioManager.getMode() != AudioManager.MODE_IN_COMMUNICATION) { 1078 throw new IllegalStateException( 1079 "Only enable SettingsObserver in COMM mode"); 1080 } 1081 1082 // Get stream volume for the voice stream and deliver callback if 1083 // the volume index is zero. It is not possible to move the volume 1084 // slider all the way down in communication mode but the callback 1085 // implementation can ensure that the volume is completely muted. 1086 int volume = mAudioManager.getStreamVolume( 1087 AudioManager.STREAM_VOICE_CALL); 1088 if (DEBUG) logd("nativeSetMute: " + (volume == 0)); 1089 nativeSetMute(mNativeAudioManagerAndroid, (volume == 0)); 1090 } 1091 }; 1092 1093 mContentResolver.registerContentObserver( 1094 Settings.System.CONTENT_URI, true, mSettingsObserver); 1095 } 1096 1097 /** Quit observer thread and stop listening for volume changes. */ 1098 private void stopObservingVolumeChanges() { 1099 if (DEBUG) logd("stopObservingVolumeChanges"); 1100 if (mSettingsObserverThread == null) 1101 return; 1102 1103 mContentResolver.unregisterContentObserver(mSettingsObserver); 1104 mSettingsObserver = null; 1105 1106 mSettingsObserverThread.quit(); 1107 try { 1108 mSettingsObserverThread.join(); 1109 } catch (InterruptedException e) { 1110 Log.e(TAG, "Thread.join() exception: ", e); 1111 } 1112 mSettingsObserverThread = null; 1113 } 1114 1115 private native void nativeSetMute(long nativeAudioManagerAndroid, boolean muted); 1116} 1117