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 5 package org.chromium.media; 6 7 import android.bluetooth.BluetoothAdapter; 8 import android.bluetooth.BluetoothManager; 9 import android.content.BroadcastReceiver; 10 import android.content.ContentResolver; 11 import android.content.Context; 12 import android.content.Intent; 13 import android.content.IntentFilter; 14 import android.content.pm.PackageManager; 15 import android.database.ContentObserver; 16 import android.media.AudioFormat; 17 import android.media.AudioManager; 18 import android.media.AudioRecord; 19 import android.media.AudioTrack; 20 import android.media.audiofx.AcousticEchoCanceler; 21 import android.os.Build; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.os.Process; 25 import android.provider.Settings; 26 import android.util.Log; 27 28 import org.chromium.base.CalledByNative; 29 import org.chromium.base.JNINamespace; 30 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.List; 34 35 @JNINamespace("media") 36 class AudioManagerAndroid { 37 private static final String TAG = "AudioManagerAndroid"; 38 39 // Set to true to enable debug logs. Always check in as false. 40 private static final boolean DEBUG = false; 41 42 /** Simple container for device information. */ 43 private static class AudioDeviceName { 44 private final int mId; 45 private final String mName; 46 47 private AudioDeviceName(int id, String name) { 48 mId = id; 49 mName = name; 50 } 51 52 @CalledByNative("AudioDeviceName") 53 private String id() { return String.valueOf(mId); } 54 55 @CalledByNative("AudioDeviceName") 56 private String name() { return mName; } 57 } 58 59 // Supported audio device types. 60 private static final int DEVICE_INVALID = -1; 61 private static final int DEVICE_SPEAKERPHONE = 0; 62 private static final int DEVICE_WIRED_HEADSET = 1; 63 private static final int DEVICE_EARPIECE = 2; 64 private static final int DEVICE_BLUETOOTH_HEADSET = 3; 65 private static final int DEVICE_COUNT = 4; 66 67 // Maps audio device types to string values. This map must be in sync 68 // with the device types above. 69 // TODO(henrika): add support for proper detection of device names and 70 // localize the name strings by using resource strings. 71 private static final String[] DEVICE_NAMES = new String[] { 72 "Speakerphone", 73 "Wired headset", // With or without microphone 74 "Headset earpiece", // Only available on mobile phones 75 "Bluetooth headset", 76 }; 77 78 // List of valid device types. 79 private static final Integer[] VALID_DEVICES = new Integer[] { 80 DEVICE_SPEAKERPHONE, 81 DEVICE_WIRED_HEADSET, 82 DEVICE_EARPIECE, 83 DEVICE_BLUETOOTH_HEADSET, 84 }; 85 86 // The device does not have any audio device. 87 static final int STATE_NO_DEVICE_SELECTED = 0; 88 // The speakerphone is on and an associated microphone is used. 89 static final int STATE_SPEAKERPHONE_ON = 1; 90 // The phone's earpiece is on and an associated microphone is used. 91 static final int STATE_EARPIECE_ON = 2; 92 // A wired headset (with or without a microphone) is plugged in. 93 static final int STATE_WIRED_HEADSET_ON = 3; 94 // The audio stream is being directed to a Bluetooth headset. 95 static final int STATE_BLUETOOTH_ON = 4; 96 // We've requested that the audio stream be directed to Bluetooth, but 97 // have not yet received a response from the framework. 98 static final int STATE_BLUETOOTH_TURNING_ON = 5; 99 // We've requested that the audio stream stop being directed to 100 // Bluetooth, but have not yet received a response from the framework. 101 static final int STATE_BLUETOOTH_TURNING_OFF = 6; 102 // TODO(henrika): document the valid state transitions. 103 104 // Use 44.1kHz as the default sampling rate. 105 private static final int DEFAULT_SAMPLING_RATE = 44100; 106 // Randomly picked up frame size which is close to return value on N4. 107 // Return this value when getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER) 108 // fails. 109 private static final int DEFAULT_FRAME_PER_BUFFER = 256; 110 111 private final AudioManager mAudioManager; 112 private final Context mContext; 113 private final long mNativeAudioManagerAndroid; 114 115 private boolean mHasBluetoothPermission = false; 116 private boolean mIsInitialized = false; 117 private boolean mSavedIsSpeakerphoneOn; 118 private boolean mSavedIsMicrophoneMute; 119 120 private Integer mAudioDeviceState = STATE_NO_DEVICE_SELECTED; 121 122 // Lock to protect |mAudioDevices| which can be accessed from the main 123 // thread and the audio manager thread. 124 private final Object mLock = new Object(); 125 126 // Contains a list of currently available audio devices. 127 private boolean[] mAudioDevices = new boolean[DEVICE_COUNT]; 128 129 private final ContentResolver mContentResolver; 130 private SettingsObserver mSettingsObserver = null; 131 private SettingsObserverThread mSettingsObserverThread = null; 132 private int mCurrentVolume; 133 private final Object mSettingsObserverLock = new Object(); 134 135 // Broadcast receiver for wired headset intent broadcasts. 136 private BroadcastReceiver mWiredHeadsetReceiver; 137 138 /** Construction */ 139 @CalledByNative 140 private static AudioManagerAndroid createAudioManagerAndroid( 141 Context context, 142 long nativeAudioManagerAndroid) { 143 return new AudioManagerAndroid(context, nativeAudioManagerAndroid); 144 } 145 146 private AudioManagerAndroid(Context context, long nativeAudioManagerAndroid) { 147 mContext = context; 148 mNativeAudioManagerAndroid = nativeAudioManagerAndroid; 149 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 150 mContentResolver = mContext.getContentResolver(); 151 } 152 153 /** 154 * Saves the initial speakerphone and microphone state. 155 * Populates the list of available audio devices and registers receivers 156 * for broadcasted intents related to wired headset and bluetooth devices. 157 */ 158 @CalledByNative 159 public void init() { 160 if (mIsInitialized) 161 return; 162 163 synchronized (mLock) { 164 for (int i = 0; i < DEVICE_COUNT; ++i) { 165 mAudioDevices[i] = false; 166 } 167 } 168 169 // Store microphone mute state and speakerphone state so it can 170 // be restored when closing. 171 mSavedIsSpeakerphoneOn = mAudioManager.isSpeakerphoneOn(); 172 mSavedIsMicrophoneMute = mAudioManager.isMicrophoneMute(); 173 174 // Initialize audio device list with things we know is always available. 175 synchronized (mLock) { 176 if (hasEarpiece()) { 177 mAudioDevices[DEVICE_EARPIECE] = true; 178 } 179 mAudioDevices[DEVICE_SPEAKERPHONE] = true; 180 } 181 182 // Register receiver for broadcasted intents related to adding/ 183 // removing a wired headset (Intent.ACTION_HEADSET_PLUG). 184 // Also starts routing to the wired headset/headphone if one is 185 // already attached (can be overridden by a Bluetooth headset). 186 registerForWiredHeadsetIntentBroadcast(); 187 188 // Start routing to Bluetooth if there's a connected device. 189 // TODO(henrika): the actual routing part is not implemented yet. 190 // All we do currently is to detect if BT headset is attached or not. 191 initBluetooth(); 192 193 mIsInitialized = true; 194 195 mSettingsObserverThread = new SettingsObserverThread(); 196 synchronized (mSettingsObserverLock) { 197 try { 198 mSettingsObserverThread.start(); 199 mSettingsObserverLock.wait(); 200 } catch (InterruptedException e) { 201 Log.e(TAG, "unregisterHeadsetReceiver exception: " + e.getMessage()); 202 } 203 } 204 } 205 206 /** 207 * Unregister all previously registered intent receivers and restore 208 * the stored state (stored in {@link #init()}). 209 */ 210 @CalledByNative 211 public void close() { 212 if (!mIsInitialized) 213 return; 214 215 if (mSettingsObserverThread != null) { 216 mSettingsObserverThread = null; 217 } 218 if (mSettingsObserver != null) { 219 mContentResolver.unregisterContentObserver(mSettingsObserver); 220 mSettingsObserver = null; 221 } 222 223 unregisterForWiredHeadsetIntentBroadcast(); 224 225 // Restore previously stored audio states. 226 setMicrophoneMute(mSavedIsMicrophoneMute); 227 setSpeakerphoneOn(mSavedIsSpeakerphoneOn); 228 229 mIsInitialized = false; 230 } 231 232 @CalledByNative 233 public void setMode(int mode) { 234 try { 235 mAudioManager.setMode(mode); 236 } catch (SecurityException e) { 237 Log.e(TAG, "setMode exception: " + e.getMessage()); 238 logDeviceInfo(); 239 } 240 } 241 242 /** 243 * Activates, i.e., starts routing audio to, the specified audio device. 244 * 245 * @param deviceId Unique device ID (integer converted to string) 246 * representing the selected device. This string is empty if the so-called 247 * default device is selected. 248 */ 249 @CalledByNative 250 public void setDevice(String deviceId) { 251 boolean devices[] = null; 252 synchronized (mLock) { 253 devices = mAudioDevices.clone(); 254 } 255 if (deviceId.isEmpty()) { 256 logd("setDevice: default"); 257 // Use a special selection scheme if the default device is selected. 258 // The "most unique" device will be selected; Bluetooth first, then 259 // wired headset and last the speaker phone. 260 if (devices[DEVICE_BLUETOOTH_HEADSET]) { 261 // TODO(henrika): possibly need improvements here if we are 262 // in a STATE_BLUETOOTH_TURNING_OFF state. 263 setAudioDevice(DEVICE_BLUETOOTH_HEADSET); 264 } else if (devices[DEVICE_WIRED_HEADSET]) { 265 setAudioDevice(DEVICE_WIRED_HEADSET); 266 } else { 267 setAudioDevice(DEVICE_SPEAKERPHONE); 268 } 269 } else { 270 logd("setDevice: " + deviceId); 271 // A non-default device is specified. Verify that it is valid 272 // device, and if so, start using it. 273 List<Integer> validIds = Arrays.asList(VALID_DEVICES); 274 Integer id = Integer.valueOf(deviceId); 275 if (validIds.contains(id)) { 276 setAudioDevice(id.intValue()); 277 } else { 278 loge("Invalid device ID!"); 279 } 280 } 281 } 282 283 /** 284 * @return the current list of available audio devices. 285 * Note that this call does not trigger any update of the list of devices, 286 * it only copies the current state in to the output array. 287 */ 288 @CalledByNative 289 public AudioDeviceName[] getAudioInputDeviceNames() { 290 synchronized (mLock) { 291 List<String> devices = new ArrayList<String>(); 292 AudioDeviceName[] array = new AudioDeviceName[getNumOfAudioDevicesWithLock()]; 293 int i = 0; 294 for (int id = 0; id < DEVICE_COUNT; ++id) { 295 if (mAudioDevices[id]) { 296 array[i] = new AudioDeviceName(id, DEVICE_NAMES[id]); 297 devices.add(DEVICE_NAMES[id]); 298 i++; 299 } 300 } 301 logd("getAudioInputDeviceNames: " + devices); 302 return array; 303 } 304 } 305 306 @CalledByNative 307 private int getNativeOutputSampleRate() { 308 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 309 String sampleRateString = mAudioManager.getProperty( 310 AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 311 return (sampleRateString == null ? 312 DEFAULT_SAMPLING_RATE : Integer.parseInt(sampleRateString)); 313 } else { 314 return DEFAULT_SAMPLING_RATE; 315 } 316 } 317 318 /** 319 * Returns the minimum frame size required for audio input. 320 * 321 * @param sampleRate sampling rate 322 * @param channels number of channels 323 */ 324 @CalledByNative 325 private static int getMinInputFrameSize(int sampleRate, int channels) { 326 int channelConfig; 327 if (channels == 1) { 328 channelConfig = AudioFormat.CHANNEL_IN_MONO; 329 } else if (channels == 2) { 330 channelConfig = AudioFormat.CHANNEL_IN_STEREO; 331 } else { 332 return -1; 333 } 334 return AudioRecord.getMinBufferSize( 335 sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels; 336 } 337 338 /** 339 * Returns the minimum frame size required for audio output. 340 * 341 * @param sampleRate sampling rate 342 * @param channels number of channels 343 */ 344 @CalledByNative 345 private static int getMinOutputFrameSize(int sampleRate, int channels) { 346 int channelConfig; 347 if (channels == 1) { 348 channelConfig = AudioFormat.CHANNEL_OUT_MONO; 349 } else if (channels == 2) { 350 channelConfig = AudioFormat.CHANNEL_OUT_STEREO; 351 } else { 352 return -1; 353 } 354 return AudioTrack.getMinBufferSize( 355 sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels; 356 } 357 358 @CalledByNative 359 private boolean isAudioLowLatencySupported() { 360 return mContext.getPackageManager().hasSystemFeature( 361 PackageManager.FEATURE_AUDIO_LOW_LATENCY); 362 } 363 364 @CalledByNative 365 private int getAudioLowLatencyOutputFrameSize() { 366 String framesPerBuffer = 367 mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 368 return (framesPerBuffer == null ? 369 DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer)); 370 } 371 372 @CalledByNative 373 public static boolean shouldUseAcousticEchoCanceler() { 374 // AcousticEchoCanceler was added in API level 16 (Jelly Bean). 375 // Next is a list of device models which have been vetted for good 376 // quality platform echo cancellation. 377 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && 378 AcousticEchoCanceler.isAvailable() && 379 (Build.MODEL.equals("Nexus 5") || 380 Build.MODEL.equals("Nexus 7")); 381 } 382 383 /** Sets the speaker phone mode. */ 384 public void setSpeakerphoneOn(boolean on) { 385 boolean wasOn = mAudioManager.isSpeakerphoneOn(); 386 if (wasOn == on) { 387 return; 388 } 389 mAudioManager.setSpeakerphoneOn(on); 390 } 391 392 /** Sets the microphone mute state. */ 393 public void setMicrophoneMute(boolean on) { 394 boolean wasMuted = mAudioManager.isMicrophoneMute(); 395 if (wasMuted == on) { 396 return; 397 } 398 mAudioManager.setMicrophoneMute(on); 399 } 400 401 /** Gets the current microphone mute state. */ 402 public boolean isMicrophoneMute() { 403 return mAudioManager.isMicrophoneMute(); 404 } 405 406 /** Gets the current earpice state. */ 407 private boolean hasEarpiece() { 408 return mContext.getPackageManager().hasSystemFeature( 409 PackageManager.FEATURE_TELEPHONY); 410 } 411 412 /** 413 * Registers receiver for the broadcasted intent when a wired headset is 414 * plugged in or unplugged. The received intent will have an extra 415 * 'state' value where 0 means unplugged, and 1 means plugged. 416 */ 417 private void registerForWiredHeadsetIntentBroadcast() { 418 IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); 419 420 /** 421 * Receiver which handles changes in wired headset availablilty. 422 */ 423 mWiredHeadsetReceiver = new BroadcastReceiver() { 424 private static final int STATE_UNPLUGGED = 0; 425 private static final int STATE_PLUGGED = 1; 426 private static final int HAS_NO_MIC = 0; 427 private static final int HAS_MIC = 1; 428 429 @Override 430 public void onReceive(Context context, Intent intent) { 431 String action = intent.getAction(); 432 if (!action.equals(Intent.ACTION_HEADSET_PLUG)) { 433 return; 434 } 435 int state = intent.getIntExtra("state", STATE_UNPLUGGED); 436 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); 437 String name = intent.getStringExtra("name"); 438 logd("==> onReceive: s=" + state 439 + ", m=" + microphone 440 + ", n=" + name 441 + ", sb=" + isInitialStickyBroadcast()); 442 443 switch (state) { 444 case STATE_UNPLUGGED: 445 synchronized (mLock) { 446 // Wired headset and earpiece are mutually exclusive. 447 mAudioDevices[DEVICE_WIRED_HEADSET] = false; 448 if (hasEarpiece()) { 449 mAudioDevices[DEVICE_EARPIECE] = true; 450 } 451 } 452 // If wired headset was used before it was unplugged, 453 // switch to speaker phone. If it was not in use; just 454 // log the change. 455 if (mAudioDeviceState == STATE_WIRED_HEADSET_ON) { 456 setAudioDevice(DEVICE_SPEAKERPHONE); 457 } else { 458 reportUpdate(); 459 } 460 break; 461 case STATE_PLUGGED: 462 synchronized (mLock) { 463 // Wired headset and earpiece are mutually exclusive. 464 mAudioDevices[DEVICE_WIRED_HEADSET] = true; 465 mAudioDevices[DEVICE_EARPIECE] = false; 466 setAudioDevice(DEVICE_WIRED_HEADSET); 467 } 468 break; 469 default: 470 loge("Invalid state!"); 471 break; 472 } 473 } 474 }; 475 476 // Note: the intent we register for here is sticky, so it'll tell us 477 // immediately what the last action was (plugged or unplugged). 478 // It will enable us to set the speakerphone correctly. 479 mContext.registerReceiver(mWiredHeadsetReceiver, filter); 480 } 481 482 /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */ 483 private void unregisterForWiredHeadsetIntentBroadcast() { 484 mContext.unregisterReceiver(mWiredHeadsetReceiver); 485 mWiredHeadsetReceiver = null; 486 } 487 488 /** 489 * Check if Bluetooth device is connected, register Bluetooth receiver 490 * and start routing to Bluetooth if a device is connected. 491 * TODO(henrika): currently only supports the detecion part at startup. 492 */ 493 private void initBluetooth() { 494 // Bail out if we don't have the required permission. 495 mHasBluetoothPermission = mContext.checkPermission( 496 android.Manifest.permission.BLUETOOTH, 497 Process.myPid(), 498 Process.myUid()) == PackageManager.PERMISSION_GRANTED; 499 if (!mHasBluetoothPermission) { 500 loge("BLUETOOTH permission is missing!"); 501 return; 502 } 503 504 // To get a BluetoothAdapter representing the local Bluetooth adapter, 505 // when running on JELLY_BEAN_MR1 (4.2) and below, call the static 506 // getDefaultAdapter() method; when running on JELLY_BEAN_MR2 (4.3) and 507 // higher, retrieve it through getSystemService(String) with 508 // BLUETOOTH_SERVICE. 509 // Note: Most methods require the BLUETOOTH permission. 510 BluetoothAdapter btAdapter = null; 511 if (android.os.Build.VERSION.SDK_INT <= 512 android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { 513 // Use static method for Android 4.2 and below to get the 514 // BluetoothAdapter. 515 btAdapter = BluetoothAdapter.getDefaultAdapter(); 516 } else { 517 // Use BluetoothManager to get the BluetoothAdapter for 518 // Android 4.3 and above. 519 BluetoothManager btManager = (BluetoothManager) mContext.getSystemService( 520 Context.BLUETOOTH_SERVICE); 521 btAdapter = btManager.getAdapter(); 522 } 523 524 if (btAdapter != null && 525 // android.bluetooth.BluetoothAdapter.getProfileConnectionState 526 // requires BLUETOOTH permission. 527 android.bluetooth.BluetoothProfile.STATE_CONNECTED == 528 btAdapter.getProfileConnectionState( 529 android.bluetooth.BluetoothProfile.HEADSET)) { 530 synchronized (mLock) { 531 mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = true; 532 } 533 // TODO(henrika): ensure that we set the active audio 534 // device to Bluetooth (not trivial). 535 setAudioDevice(DEVICE_BLUETOOTH_HEADSET); 536 } 537 } 538 539 /** 540 * Changes selection of the currently active audio device. 541 * 542 * @param device Specifies the selected audio device. 543 */ 544 public void setAudioDevice(int device) { 545 switch (device) { 546 case DEVICE_BLUETOOTH_HEADSET: 547 // TODO(henrika): add support for turning on an routing to 548 // BT here. 549 if (DEBUG) logd("--- TO BE IMPLEMENTED ---"); 550 break; 551 case DEVICE_SPEAKERPHONE: 552 // TODO(henrika): turn off BT if required. 553 mAudioDeviceState = STATE_SPEAKERPHONE_ON; 554 setSpeakerphoneOn(true); 555 break; 556 case DEVICE_WIRED_HEADSET: 557 // TODO(henrika): turn off BT if required. 558 mAudioDeviceState = STATE_WIRED_HEADSET_ON; 559 setSpeakerphoneOn(false); 560 break; 561 case DEVICE_EARPIECE: 562 // TODO(henrika): turn off BT if required. 563 mAudioDeviceState = STATE_EARPIECE_ON; 564 setSpeakerphoneOn(false); 565 break; 566 default: 567 loge("Invalid audio device selection!"); 568 break; 569 } 570 reportUpdate(); 571 } 572 573 private int getNumOfAudioDevicesWithLock() { 574 int count = 0; 575 for (int i = 0; i < DEVICE_COUNT; ++i) { 576 if (mAudioDevices[i]) 577 count++; 578 } 579 return count; 580 } 581 582 /** 583 * For now, just log the state change but the idea is that we should 584 * notify a registered state change listener (if any) that there has 585 * been a change in the state. 586 * TODO(henrika): add support for state change listener. 587 */ 588 private void reportUpdate() { 589 synchronized (mLock) { 590 List<String> devices = new ArrayList<String>(); 591 for (int i = 0; i < DEVICE_COUNT; ++i) { 592 if (mAudioDevices[i]) 593 devices.add(DEVICE_NAMES[i]); 594 } 595 logd("reportUpdate: state=" + mAudioDeviceState 596 + ", devices=" + devices); 597 } 598 } 599 600 private void logDeviceInfo() { 601 Log.i(TAG, "Manufacturer:" + Build.MANUFACTURER + 602 " Board: " + Build.BOARD + " Device: " + Build.DEVICE + 603 " Model: " + Build.MODEL + " PRODUCT: " + Build.PRODUCT); 604 } 605 606 /** Trivial helper method for debug logging */ 607 private static void logd(String msg) { 608 Log.d(TAG, msg); 609 } 610 611 /** Trivial helper method for error logging */ 612 private static void loge(String msg) { 613 Log.e(TAG, msg); 614 } 615 616 private class SettingsObserver extends ContentObserver { 617 SettingsObserver() { 618 super(new Handler()); 619 mContentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, this); 620 } 621 622 @Override 623 public void onChange(boolean selfChange) { 624 super.onChange(selfChange); 625 int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL); 626 nativeSetMute(mNativeAudioManagerAndroid, (volume == 0)); 627 } 628 } 629 630 private native void nativeSetMute(long nativeAudioManagerAndroid, boolean muted); 631 632 private class SettingsObserverThread extends Thread { 633 SettingsObserverThread() { 634 super("SettinsObserver"); 635 } 636 637 @Override 638 public void run() { 639 // Set this thread up so the handler will work on it. 640 Looper.prepare(); 641 642 synchronized (mSettingsObserverLock) { 643 mSettingsObserver = new SettingsObserver(); 644 mSettingsObserverLock.notify(); 645 } 646 647 // Listen for volume change. 648 Looper.loop(); 649 } 650 } 651 } 652