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