12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved. 22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file. 42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)package org.chromium.media; 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 7a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import android.bluetooth.BluetoothAdapter; 8a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import android.bluetooth.BluetoothManager; 92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.content.BroadcastReceiver; 10a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import android.content.ContentResolver; 112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.content.Context; 122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.content.Intent; 132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.content.IntentFilter; 1490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)import android.content.pm.PackageManager; 15a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import android.database.ContentObserver; 1690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)import android.media.AudioFormat; 172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.media.AudioManager; 1890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)import android.media.AudioRecord; 1990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)import android.media.AudioTrack; 205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.media.audiofx.AcousticEchoCanceler; 21c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import android.os.Build; 22a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import android.os.Handler; 235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.os.HandlerThread; 24a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import android.os.Process; 25a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import android.provider.Settings; 26c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import android.util.Log; 272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import org.chromium.base.CalledByNative; 292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import org.chromium.base.JNINamespace; 302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 31a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import java.util.ArrayList; 32a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import java.util.Arrays; 33a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import java.util.List; 34a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@JNINamespace("media") 362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class AudioManagerAndroid { 379ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch private static final String TAG = "AudioManagerAndroid"; 382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Set to true to enable debug logs. Avoid in production builds. 405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // NOTE: always check in as false. 41a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final boolean DEBUG = false; 42a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** 44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * NonThreadSafe is a helper class used to help verify that methods of a 45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * class are called from the same thread. 46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Inspired by class in package com.google.android.apps.chrome.utilities. 47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Is only utilized when DEBUG is set to true. 48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */ 49a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private static class NonThreadSafe { 50a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private final Long mThreadId; 51a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) public NonThreadSafe() { 53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (DEBUG) { 54a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mThreadId = Thread.currentThread().getId(); 55a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } else { 56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Avoids "Unread field" issue reported by findbugs. 57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mThreadId = 0L; 58a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 59a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 61a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** 62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Checks if the method is called on the valid thread. 63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Assigns the current thread if no thread was assigned. 64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */ 65a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) public boolean calledOnValidThread() { 66a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (DEBUG) { 67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return mThreadId.equals(Thread.currentThread().getId()); 68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 69a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return true; 70a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 71a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 72a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static boolean runningOnJellyBeanOrHigher() { 745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; 755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static boolean runningOnJellyBeanMR1OrHigher() { 785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1; 795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static boolean runningOnJellyBeanMR2OrHigher() { 825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; 835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 85a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** Simple container for device information. */ 86a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static class AudioDeviceName { 87a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private final int mId; 88a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private final String mName; 89a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 90a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private AudioDeviceName(int id, String name) { 91a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mId = id; 92a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mName = name; 93a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 94a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 95a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) @CalledByNative("AudioDeviceName") 96a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private String id() { return String.valueOf(mId); } 97a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 98a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) @CalledByNative("AudioDeviceName") 99a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private String name() { return mName; } 100a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 101a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // List if device models which have been vetted for good quality platform 103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // echo cancellation. 104a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // NOTE: only add new devices to this list if manual tests have been 105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // performed where the AEC performance is evaluated using e.g. a WebRTC 106a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // audio client such as https://apprtc.appspot.com/?r=<ROOM NAME>. 107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private static final String[] SUPPORTED_AEC_MODELS = new String[] { 108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "GT-I9300", // Galaxy S3 109a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "GT-I9500", // Galaxy S4 110a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "GT-N7105", // Galaxy Note 2 111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Nexus 4", // Nexus 4 112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Nexus 5", // Nexus 5 113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Nexus 7", // Nexus 7 114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "SM-N9005", // Galaxy Note 3 115a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "SM-T310", // Galaxy Tab 3 8.0 (WiFi) 116a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) }; 117a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 118a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // Supported audio device types. 1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static final int DEVICE_DEFAULT = -2; 120a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final int DEVICE_INVALID = -1; 121a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final int DEVICE_SPEAKERPHONE = 0; 122a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final int DEVICE_WIRED_HEADSET = 1; 123a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final int DEVICE_EARPIECE = 2; 124a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final int DEVICE_BLUETOOTH_HEADSET = 3; 125a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final int DEVICE_COUNT = 4; 126a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 127a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // Maps audio device types to string values. This map must be in sync 128a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // with the device types above. 129a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // TODO(henrika): add support for proper detection of device names and 130a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // localize the name strings by using resource strings. 1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // See http://crbug.com/333208 for details. 132a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final String[] DEVICE_NAMES = new String[] { 133a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "Speakerphone", 1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "Wired headset", // With or without microphone. 1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "Headset earpiece", // Only available on mobile phones. 1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "Bluetooth headset", // Requires BLUETOOTH permission. 137a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) }; 138a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 139a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // List of valid device types. 140a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final Integer[] VALID_DEVICES = new Integer[] { 141a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) DEVICE_SPEAKERPHONE, 142a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) DEVICE_WIRED_HEADSET, 143a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) DEVICE_EARPIECE, 144a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) DEVICE_BLUETOOTH_HEADSET, 145a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) }; 146a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Bluetooth audio SCO states. Example of valid state sequence: 1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // SCO_INVALID -> SCO_TURNING_ON -> SCO_ON -> SCO_TURNING_OFF -> SCO_OFF. 1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static final int STATE_BLUETOOTH_SCO_INVALID = -1; 1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static final int STATE_BLUETOOTH_SCO_OFF = 0; 1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static final int STATE_BLUETOOTH_SCO_ON = 1; 1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static final int STATE_BLUETOOTH_SCO_TURNING_ON = 2; 1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static final int STATE_BLUETOOTH_SCO_TURNING_OFF = 3; 154a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 155a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // Use 44.1kHz as the default sampling rate. 15690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) private static final int DEFAULT_SAMPLING_RATE = 44100; 15790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) // Randomly picked up frame size which is close to return value on N4. 158a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // Return this value when getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER) 159a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // fails. 16090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) private static final int DEFAULT_FRAME_PER_BUFFER = 256; 16190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) private final AudioManager mAudioManager; 1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) private final Context mContext; 164a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private final long mNativeAudioManagerAndroid; 1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 166f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Enabled during initialization if MODIFY_AUDIO_SETTINGS permission is 167f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // granted. Required to shift system-wide audio settings. 168f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) private boolean mHasModifyAudioSettingsPermission = false; 169f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 170f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Enabled during initialization if RECORD_AUDIO permission is granted. 171f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) private boolean mHasRecordAudioPermission = false; 172f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Enabled during initialization if BLUETOOTH permission is granted. 174a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private boolean mHasBluetoothPermission = false; 1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private int mSavedAudioMode = AudioManager.MODE_INVALID; 1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Stores the audio states related to Bluetooth SCO audio, where some 1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // states are needed to keep track of intermediate states while the SCO 1805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // channel is enabled or disabled (switching state can take a few seconds). 1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private int mBluetoothScoState = STATE_BLUETOOTH_SCO_INVALID; 1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 183a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private boolean mIsInitialized = false; 184a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private boolean mSavedIsSpeakerphoneOn; 185a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private boolean mSavedIsMicrophoneMute; 1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Id of the requested audio device. Can only be modified by 1885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // call to setDevice(). 1895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private int mRequestedAudioDevice = DEVICE_INVALID; 190a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 191a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // This class should be created, initialized and closed on the audio thread 192a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // in the audio manager. We use |mNonThreadSafe| to ensure that this is 193a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // the case. Only active when |DEBUG| is set to true. 194a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private final NonThreadSafe mNonThreadSafe = new NonThreadSafe(); 195a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Lock to protect |mAudioDevices| and |mRequestedAudioDevice| which can 1975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // be accessed from the main thread and the audio manager thread. 198a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private final Object mLock = new Object(); 199a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 200a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // Contains a list of currently available audio devices. 201a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private boolean[] mAudioDevices = new boolean[DEVICE_COUNT]; 2022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 203a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private final ContentResolver mContentResolver; 204a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private ContentObserver mSettingsObserver = null; 2055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private HandlerThread mSettingsObserverThread = null; 206a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private int mCurrentVolume; 207a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 208a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // Broadcast receiver for wired headset intent broadcasts. 209a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private BroadcastReceiver mWiredHeadsetReceiver; 210a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Broadcast receiver for Bluetooth headset intent broadcasts. 2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Utilized to detect changes in Bluetooth headset availability. 2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private BroadcastReceiver mBluetoothHeadsetReceiver; 2145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Broadcast receiver for Bluetooth SCO broadcasts. 2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Utilized to detect if BT SCO streaming is on or off. 2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private BroadcastReceiver mBluetoothScoReceiver; 2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 219a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** Construction */ 2202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) @CalledByNative 221a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static AudioManagerAndroid createAudioManagerAndroid( 222a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) Context context, 223a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) long nativeAudioManagerAndroid) { 224a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) return new AudioManagerAndroid(context, nativeAudioManagerAndroid); 2252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 2262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 227a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private AudioManagerAndroid(Context context, long nativeAudioManagerAndroid) { 2282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) mContext = context; 229a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mNativeAudioManagerAndroid = nativeAudioManagerAndroid; 230a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 231a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mContentResolver = mContext.getContentResolver(); 2322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 2332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 234a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** 235a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * Saves the initial speakerphone and microphone state. 236a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * Populates the list of available audio devices and registers receivers 2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * for broadcast intents related to wired headset and Bluetooth devices. 238a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) */ 2392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) @CalledByNative 2405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void init() { 241a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) checkIfCalledOnValidThread(); 2425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) logd("init"); 243a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (DEBUG) logDeviceInfo(); 244a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) if (mIsInitialized) 2452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return; 246a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 247f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Check if process has MODIFY_AUDIO_SETTINGS and RECORD_AUDIO 248f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // permissions. Both are required for full functionality. 249f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mHasModifyAudioSettingsPermission = hasPermission( 250f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) android.Manifest.permission.MODIFY_AUDIO_SETTINGS); 251f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (DEBUG && !mHasModifyAudioSettingsPermission) { 252f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) logd("MODIFY_AUDIO_SETTINGS permission is missing"); 253f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 254f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mHasRecordAudioPermission = hasPermission( 255f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) android.Manifest.permission.RECORD_AUDIO); 256f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (DEBUG && !mHasRecordAudioPermission) { 257f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) logd("RECORD_AUDIO permission is missing"); 258f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 259f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 260a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // Initialize audio device list with things we know is always available. 261a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mAudioDevices[DEVICE_EARPIECE] = hasEarpiece(); 262a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mAudioDevices[DEVICE_WIRED_HEADSET] = hasWiredHeadset(); 2635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mAudioDevices[DEVICE_SPEAKERPHONE] = true; 2642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Register receivers for broadcast intents related to Bluetooth device 2665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // and Bluetooth SCO notifications. Requires BLUETOOTH permission. 2675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) registerBluetoothIntentsIfNeeded(); 2685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Register receiver for broadcast intents related to adding/ 270a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // removing a wired headset (Intent.ACTION_HEADSET_PLUG). 271a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) registerForWiredHeadsetIntentBroadcast(); 272a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 273a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mIsInitialized = true; 274a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 2755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) reportUpdate(); 2762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 2772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 278a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** 279a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * Unregister all previously registered intent receivers and restore 280a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * the stored state (stored in {@link #init()}). 281a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) */ 2822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) @CalledByNative 2835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void close() { 284a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) checkIfCalledOnValidThread(); 2855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) logd("close"); 286a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) if (!mIsInitialized) 287a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) return; 288a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 289a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) stopObservingVolumeChanges(); 290a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) unregisterForWiredHeadsetIntentBroadcast(); 2915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) unregisterBluetoothIntentsIfNeeded(); 292a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 293a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mIsInitialized = false; 2942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 295c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 2965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 2975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Saves current audio mode and sets audio mode to MODE_IN_COMMUNICATION 2985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * if input parameter is true. Restores saved audio mode if input parameter 2995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * is false. 300f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Required permission: android.Manifest.permission.MODIFY_AUDIO_SETTINGS. 3015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 302a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) @CalledByNative 3035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void setCommunicationAudioModeOn(boolean on) { 3045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) logd("setCommunicationAudioModeOn(" + on + ")"); 3055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 306f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // The MODIFY_AUDIO_SETTINGS permission is required to allow an 307f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // application to modify global audio settings. 308f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (!mHasModifyAudioSettingsPermission) { 309f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) Log.w(TAG, "MODIFY_AUDIO_SETTINGS is missing => client will run " + 310f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) "with reduced functionality"); 311f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return; 312f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 313f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 3145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (on) { 3155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (mSavedAudioMode != AudioManager.MODE_INVALID) { 3166d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) throw new IllegalStateException("Audio mode has already been set"); 3175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 3185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 3195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Store the current audio mode the first time we try to 3205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // switch to communication mode. 3215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) try { 3225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mSavedAudioMode = mAudioManager.getMode(); 3235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } catch (SecurityException e) { 3245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) logDeviceInfo(); 3256d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) throw e; 3266d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) 3275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 3285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 3295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Store microphone mute state and speakerphone state so it can 3305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // be restored when closing. 3315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mSavedIsSpeakerphoneOn = mAudioManager.isSpeakerphoneOn(); 3325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mSavedIsMicrophoneMute = mAudioManager.isMicrophoneMute(); 3335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 3345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) try { 3355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); 3365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } catch (SecurityException e) { 3375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) logDeviceInfo(); 3386d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) throw e; 3395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 340a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 341a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Start observing volume changes to detect when the 342a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // voice/communication stream volume is at its lowest level. 343a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // It is only possible to pull down the volume slider to about 20% 344a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // of the absolute minimum (slider at far left) in communication 345a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // mode but we want to be able to mute it completely. 346a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) startObservingVolumeChanges(); 347a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 3485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } else { 3495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (mSavedAudioMode == AudioManager.MODE_INVALID) { 3506d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) throw new IllegalStateException("Audio mode has not yet been set"); 3515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 3525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 353a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) stopObservingVolumeChanges(); 354a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 3555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Restore previously stored audio states. 3565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) setMicrophoneMute(mSavedIsMicrophoneMute); 3575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) setSpeakerphoneOn(mSavedIsSpeakerphoneOn); 3585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 3595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Restore the mode that was used before we switched to 3605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // communication mode. 3615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) try { 3625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mAudioManager.setMode(mSavedAudioMode); 3635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } catch (SecurityException e) { 3645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) logDeviceInfo(); 3656d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) throw e; 3665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 3675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mSavedAudioMode = AudioManager.MODE_INVALID; 368a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 369a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 370a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 371a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** 372a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * Activates, i.e., starts routing audio to, the specified audio device. 373a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * 374a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * @param deviceId Unique device ID (integer converted to string) 375a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * representing the selected device. This string is empty if the so-called 3765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * default device is requested. 377f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Required permissions: android.Manifest.permission.MODIFY_AUDIO_SETTINGS 378f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * and android.Manifest.permission.RECORD_AUDIO. 379a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) */ 380a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) @CalledByNative 3815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private boolean setDevice(String deviceId) { 3825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) logd("setDevice: " + deviceId); 3835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!mIsInitialized) 3845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return false; 385f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (!mHasModifyAudioSettingsPermission || !mHasRecordAudioPermission) { 386f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) Log.w(TAG, "Requires MODIFY_AUDIO_SETTINGS and RECORD_AUDIO"); 387f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) Log.w(TAG, "Selected device will not be available for recording"); 388f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return false; 389f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 390f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 3915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int intDeviceId = deviceId.isEmpty() ? 3925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) DEVICE_DEFAULT : Integer.parseInt(deviceId); 3935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 3945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (intDeviceId == DEVICE_DEFAULT) { 3955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) boolean devices[] = null; 3965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) synchronized (mLock) { 3975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) devices = mAudioDevices.clone(); 3985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mRequestedAudioDevice = DEVICE_DEFAULT; 399a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 4005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int defaultDevice = selectDefaultDevice(devices); 4015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) setAudioDevice(defaultDevice); 4025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return true; 4035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 4045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 4055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // A non-default device is specified. Verify that it is valid 4065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // device, and if so, start using it. 4075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) List<Integer> validIds = Arrays.asList(VALID_DEVICES); 4085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!validIds.contains(intDeviceId) || !mAudioDevices[intDeviceId]) { 4095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return false; 4105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 4115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) synchronized (mLock) { 4125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mRequestedAudioDevice = intDeviceId; 413a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 4145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) setAudioDevice(intDeviceId); 4155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return true; 416a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 417a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 418a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** 419a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * @return the current list of available audio devices. 420a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * Note that this call does not trigger any update of the list of devices, 421a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * it only copies the current state in to the output array. 422f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Required permissions: android.Manifest.permission.MODIFY_AUDIO_SETTINGS 423f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * and android.Manifest.permission.RECORD_AUDIO. 424a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) */ 425a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) @CalledByNative 4265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private AudioDeviceName[] getAudioInputDeviceNames() { 427a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (DEBUG) logd("getAudioInputDeviceNames"); 4285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!mIsInitialized) 4295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return null; 430f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (!mHasModifyAudioSettingsPermission || !mHasRecordAudioPermission) { 431f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) Log.w(TAG, "Requires MODIFY_AUDIO_SETTINGS and RECORD_AUDIO"); 432f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) Log.w(TAG, "No audio device will be available for recording"); 433f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return null; 434f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 435f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 4365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) boolean devices[] = null; 437a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) synchronized (mLock) { 4385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) devices = mAudioDevices.clone(); 4395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 4405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) List<String> list = new ArrayList<String>(); 4415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) AudioDeviceName[] array = 4425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) new AudioDeviceName[getNumOfAudioDevices(devices)]; 4435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int i = 0; 4445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (int id = 0; id < DEVICE_COUNT; ++id) { 4455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (devices[id]) { 4465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) array[i] = new AudioDeviceName(id, DEVICE_NAMES[id]); 4475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) list.add(DEVICE_NAMES[id]); 4485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) i++; 449a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 450a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 4515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) logd("getAudioInputDeviceNames: " + list); 4525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return array; 45390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 45490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 45590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) @CalledByNative 45690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) private int getNativeOutputSampleRate() { 4575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (runningOnJellyBeanMR1OrHigher()) { 45890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) String sampleRateString = mAudioManager.getProperty( 45990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 46090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return (sampleRateString == null ? 46190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) DEFAULT_SAMPLING_RATE : Integer.parseInt(sampleRateString)); 46290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } else { 46390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return DEFAULT_SAMPLING_RATE; 46490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 46590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 46690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 46790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) /** 46890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Returns the minimum frame size required for audio input. 46990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * 47090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param sampleRate sampling rate 47190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param channels number of channels 47290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */ 47390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) @CalledByNative 47490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) private static int getMinInputFrameSize(int sampleRate, int channels) { 47590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) int channelConfig; 47690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) if (channels == 1) { 47790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) channelConfig = AudioFormat.CHANNEL_IN_MONO; 47890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } else if (channels == 2) { 47990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) channelConfig = AudioFormat.CHANNEL_IN_STEREO; 48090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } else { 48190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return -1; 48290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 48390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return AudioRecord.getMinBufferSize( 48490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels; 48590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 48690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 48790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) /** 48890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Returns the minimum frame size required for audio output. 48990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * 49090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param sampleRate sampling rate 49190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param channels number of channels 49290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */ 49390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) @CalledByNative 49490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) private static int getMinOutputFrameSize(int sampleRate, int channels) { 49590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) int channelConfig; 49690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) if (channels == 1) { 49790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) channelConfig = AudioFormat.CHANNEL_OUT_MONO; 49890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } else if (channels == 2) { 49990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) channelConfig = AudioFormat.CHANNEL_OUT_STEREO; 50090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } else { 50190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return -1; 50290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 50390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return AudioTrack.getMinBufferSize( 50490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels; 50590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 50690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 50790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) @CalledByNative 50890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) private boolean isAudioLowLatencySupported() { 50990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return mContext.getPackageManager().hasSystemFeature( 51090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) PackageManager.FEATURE_AUDIO_LOW_LATENCY); 511c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) } 51290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 51390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) @CalledByNative 51490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) private int getAudioLowLatencyOutputFrameSize() { 51590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) String framesPerBuffer = 51690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 51790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return (framesPerBuffer == null ? 51890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer)); 51990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 52090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 5215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @CalledByNative 522a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private static boolean shouldUseAcousticEchoCanceler() { 5235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // AcousticEchoCanceler was added in API level 16 (Jelly Bean). 5245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!runningOnJellyBeanOrHigher()) { 5255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return false; 5265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 5275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 528a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Verify that this device is among the supported/tested models. 529a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) List<String> supportedModels = Arrays.asList(SUPPORTED_AEC_MODELS); 530a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (!supportedModels.contains(Build.MODEL)) { 5315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return false; 5325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 533a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (DEBUG && AcousticEchoCanceler.isAvailable()) { 534a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) logd("Approved for use of hardware acoustic echo canceler."); 535a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 5365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 5375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // As a final check, verify that the device supports acoustic echo 5385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // cancellation. 5395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return AcousticEchoCanceler.isAvailable(); 5405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 5415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 5425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 5436d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) * Helper method for debugging purposes. Ensures that method is 544a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * called on same thread as this object was created on. 545a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */ 546a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private void checkIfCalledOnValidThread() { 547a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (DEBUG && !mNonThreadSafe.calledOnValidThread()) { 5486d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) throw new IllegalStateException("Method is not called on valid thread"); 549a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 550a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 551a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 552a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** 5535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Register for BT intents if we have the BLUETOOTH permission. 5545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Also extends the list of available devices with a BT device if one exists. 5555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 5565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void registerBluetoothIntentsIfNeeded() { 5575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Check if this process has the BLUETOOTH permission or not. 558f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mHasBluetoothPermission = hasPermission( 559f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) android.Manifest.permission.BLUETOOTH); 5605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 5615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Add a Bluetooth headset to the list of available devices if a BT 5625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // headset is detected and if we have the BLUETOOTH permission. 5635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // We must do this initial check using a dedicated method since the 5645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // broadcasted intent BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED 5655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // is not sticky and will only be received if a BT headset is connected 5665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // after this method has been called. 5675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!mHasBluetoothPermission) { 568f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) Log.w(TAG, "Requires BLUETOOTH permission"); 5695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return; 5705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 571a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = hasBluetoothHeadset(); 5725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 5735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Register receivers for broadcast intents related to changes in 5745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Bluetooth headset availability and usage of the SCO channel. 5755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) registerForBluetoothHeadsetIntentBroadcast(); 5765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) registerForBluetoothScoIntentBroadcast(); 5775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 5785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 5795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** Unregister for BT intents if a registration has been made. */ 5805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void unregisterBluetoothIntentsIfNeeded() { 5815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (mHasBluetoothPermission) { 5825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mAudioManager.stopBluetoothSco(); 5835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) unregisterForBluetoothHeadsetIntentBroadcast(); 5845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) unregisterForBluetoothScoIntentBroadcast(); 5855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 5865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 5875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 588a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** Sets the speaker phone mode. */ 5895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void setSpeakerphoneOn(boolean on) { 590a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) boolean wasOn = mAudioManager.isSpeakerphoneOn(); 591a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) if (wasOn == on) { 592a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) return; 593a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 594a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mAudioManager.setSpeakerphoneOn(on); 595a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 596a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 597a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** Sets the microphone mute state. */ 5985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void setMicrophoneMute(boolean on) { 599a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) boolean wasMuted = mAudioManager.isMicrophoneMute(); 600a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) if (wasMuted == on) { 601a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) return; 602a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 603a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mAudioManager.setMicrophoneMute(on); 604a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 605a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 606a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** Gets the current microphone mute state. */ 6075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private boolean isMicrophoneMute() { 608a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) return mAudioManager.isMicrophoneMute(); 609a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 610a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 611a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** Gets the current earpiece state. */ 612a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private boolean hasEarpiece() { 613a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) return mContext.getPackageManager().hasSystemFeature( 614a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) PackageManager.FEATURE_TELEPHONY); 615a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 616a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 617a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** 618a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Checks whether a wired headset is connected or not. 619a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * This is not a valid indication that audio playback is actually over 620a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * the wired headset as audio routing depends on other conditions. We 621a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * only use it as an early indicator (during initialization) of an attached 622a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * wired headset. 623a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */ 624a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @Deprecated 625a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private boolean hasWiredHeadset() { 626a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return mAudioManager.isWiredHeadsetOn(); 627a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 628a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 629f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) /** Checks if the process has as specified permission or not. */ 630f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) private boolean hasPermission(String permission) { 631f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return mContext.checkPermission( 632f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) permission, 633f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) Process.myPid(), 634f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) Process.myUid()) == PackageManager.PERMISSION_GRANTED; 6355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 6365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 6375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 6385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Gets the current Bluetooth headset state. 6395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * android.bluetooth.BluetoothAdapter.getProfileConnectionState() requires 6405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * the BLUETOOTH permission. 6415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 6425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private boolean hasBluetoothHeadset() { 6435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!mHasBluetoothPermission) { 644f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) Log.w(TAG, "hasBluetoothHeadset() requires BLUETOOTH permission"); 6455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return false; 6465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 6475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 6485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // To get a BluetoothAdapter representing the local Bluetooth adapter, 6495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // when running on JELLY_BEAN_MR1 (4.2) and below, call the static 6505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // getDefaultAdapter() method; when running on JELLY_BEAN_MR2 (4.3) and 6515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // higher, retrieve it through getSystemService(String) with 6525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // BLUETOOTH_SERVICE. 6535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) BluetoothAdapter btAdapter = null; 6545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (runningOnJellyBeanMR2OrHigher()) { 6555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Use BluetoothManager to get the BluetoothAdapter for 6565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Android 4.3 and above. 6576d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) BluetoothManager btManager = 6581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci (BluetoothManager) mContext.getSystemService( 6596d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) Context.BLUETOOTH_SERVICE); 6606d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) btAdapter = btManager.getAdapter(); 6615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } else { 6625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Use static method for Android 4.2 and below to get the 6635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // BluetoothAdapter. 6646d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) btAdapter = BluetoothAdapter.getDefaultAdapter(); 6655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 6665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 6675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (btAdapter == null) { 6685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) // Bluetooth not supported on this platform. 6695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) return false; 6705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 6715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 6725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int profileConnectionState; 6736d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) profileConnectionState = btAdapter.getProfileConnectionState( 6745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) android.bluetooth.BluetoothProfile.HEADSET); 6755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 6765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Ensure that Bluetooth is enabled and that a device which supports the 6775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // headset and handsfree profile is connected. 6785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // TODO(henrika): it is possible that btAdapter.isEnabled() is 6795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // redundant. It might be sufficient to only check the profile state. 6805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return btAdapter.isEnabled() && profileConnectionState == 6815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) android.bluetooth.BluetoothProfile.STATE_CONNECTED; 6825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 6835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 684a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** 685a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * Registers receiver for the broadcasted intent when a wired headset is 686a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * plugged in or unplugged. The received intent will have an extra 687a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * 'state' value where 0 means unplugged, and 1 means plugged. 688a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) */ 689a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private void registerForWiredHeadsetIntentBroadcast() { 690a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); 691a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 6925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** Receiver which handles changes in wired headset availability. */ 693a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mWiredHeadsetReceiver = new BroadcastReceiver() { 694a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final int STATE_UNPLUGGED = 0; 695a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final int STATE_PLUGGED = 1; 696a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final int HAS_NO_MIC = 0; 697a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private static final int HAS_MIC = 1; 698a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 699a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) @Override 700a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) public void onReceive(Context context, Intent intent) { 701a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) int state = intent.getIntExtra("state", STATE_UNPLUGGED); 7025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) { 7035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); 7045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) String name = intent.getStringExtra("name"); 7055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) logd("BroadcastReceiver.onReceive: a=" + intent.getAction() + 7065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ", s=" + state + 7075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ", m=" + microphone + 7085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ", n=" + name + 7095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ", sb=" + isInitialStickyBroadcast()); 7105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 711a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) switch (state) { 712a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) case STATE_UNPLUGGED: 713a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) synchronized (mLock) { 714a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // Wired headset and earpiece are mutually exclusive. 715a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mAudioDevices[DEVICE_WIRED_HEADSET] = false; 716a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) if (hasEarpiece()) { 717a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mAudioDevices[DEVICE_EARPIECE] = true; 718a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 719a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 720a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) break; 721a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) case STATE_PLUGGED: 722a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) synchronized (mLock) { 723a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // Wired headset and earpiece are mutually exclusive. 724a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mAudioDevices[DEVICE_WIRED_HEADSET] = true; 725a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mAudioDevices[DEVICE_EARPIECE] = false; 726a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 727a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) break; 728a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) default: 729f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) loge("Invalid state"); 730a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) break; 731a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 7325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 7335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Update the existing device selection, but only if a specific 7345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // device has already been selected explicitly. 7355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (deviceHasBeenRequested()) { 7365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) updateDeviceActivation(); 7375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } else if (DEBUG) { 7385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) reportUpdate(); 7395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 740a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 741a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) }; 742a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 743a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // Note: the intent we register for here is sticky, so it'll tell us 744a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // immediately what the last action was (plugged or unplugged). 745a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) // It will enable us to set the speakerphone correctly. 746a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mContext.registerReceiver(mWiredHeadsetReceiver, filter); 747a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 748a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 749a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */ 750a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private void unregisterForWiredHeadsetIntentBroadcast() { 751a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mContext.unregisterReceiver(mWiredHeadsetReceiver); 752a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) mWiredHeadsetReceiver = null; 753a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 754a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 755a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** 7565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Registers receiver for the broadcasted intent related to BT headset 7575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * availability or a change in connection state of the local Bluetooth 7585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * adapter. Example: triggers when the BT device is turned on or off. 7595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * BLUETOOTH permission is required to receive this one. 7605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 7615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void registerForBluetoothHeadsetIntentBroadcast() { 7625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) IntentFilter filter = new IntentFilter( 7635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) android.bluetooth.BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 7645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 7655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** Receiver which handles changes in BT headset availability. */ 7665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothHeadsetReceiver = new BroadcastReceiver() { 7675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @Override 7685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) public void onReceive(Context context, Intent intent) { 7695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // A change in connection state of the Headset profile has 7705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // been detected, e.g. BT headset has been connected or 7715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // disconnected. This broadcast is *not* sticky. 7725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int profileState = intent.getIntExtra( 7735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) android.bluetooth.BluetoothHeadset.EXTRA_STATE, 7745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) android.bluetooth.BluetoothHeadset.STATE_DISCONNECTED); 7755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) { 7765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) logd("BroadcastReceiver.onReceive: a=" + intent.getAction() + 7775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ", s=" + profileState + 7785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ", sb=" + isInitialStickyBroadcast()); 7795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 7805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 7815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) switch (profileState) { 7825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) case android.bluetooth.BluetoothProfile.STATE_DISCONNECTED: 7835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // We do not have to explicitly call stopBluetoothSco() 7845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // since BT SCO will be disconnected automatically when 7855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // the BT headset is disabled. 7865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) synchronized (mLock) { 7875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Remove the BT device from the list of devices. 7885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = false; 7895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 7905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) break; 7915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) case android.bluetooth.BluetoothProfile.STATE_CONNECTED: 7925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) synchronized (mLock) { 7935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Add the BT device to the list of devices. 7945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = true; 7955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 7965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) break; 7975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) case android.bluetooth.BluetoothProfile.STATE_CONNECTING: 7985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Bluetooth service is switching from off to on. 7995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) break; 8005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) case android.bluetooth.BluetoothProfile.STATE_DISCONNECTING: 8015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Bluetooth service is switching from on to off. 8025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) break; 8035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) default: 804f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) loge("Invalid state"); 8055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) break; 8065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Update the existing device selection, but only if a specific 8095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // device has already been selected explicitly. 8105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (deviceHasBeenRequested()) { 8115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) updateDeviceActivation(); 8125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } else if (DEBUG) { 8135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) reportUpdate(); 8145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) }; 8175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mContext.registerReceiver(mBluetoothHeadsetReceiver, filter); 8195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void unregisterForBluetoothHeadsetIntentBroadcast() { 8225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mContext.unregisterReceiver(mBluetoothHeadsetReceiver); 8235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothHeadsetReceiver = null; 8245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 8275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Registers receiver for the broadcasted intent related the existence 8285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * of a BT SCO channel. Indicates if BT SCO streaming is on or off. 8295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 8305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void registerForBluetoothScoIntentBroadcast() { 8315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) IntentFilter filter = new IntentFilter( 8325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); 8335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** BroadcastReceiver implementation which handles changes in BT SCO. */ 8355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothScoReceiver = new BroadcastReceiver() { 8365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @Override 8375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) public void onReceive(Context context, Intent intent) { 8385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int state = intent.getIntExtra( 8395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) AudioManager.EXTRA_SCO_AUDIO_STATE, 8405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 8415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) { 8425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) logd("BroadcastReceiver.onReceive: a=" + intent.getAction() + 8435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ", s=" + state + 8445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ", sb=" + isInitialStickyBroadcast()); 8455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) switch (state) { 8485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) case AudioManager.SCO_AUDIO_STATE_CONNECTED: 8495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothScoState = STATE_BLUETOOTH_SCO_ON; 8505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) break; 8515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: 8525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothScoState = STATE_BLUETOOTH_SCO_OFF; 8535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) break; 8545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) case AudioManager.SCO_AUDIO_STATE_CONNECTING: 8555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // do nothing 8565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) break; 8575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) default: 858f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) loge("Invalid state"); 8595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) { 8615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) reportUpdate(); 8625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) }; 8655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mContext.registerReceiver(mBluetoothScoReceiver, filter); 8675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void unregisterForBluetoothScoIntentBroadcast() { 8705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mContext.unregisterReceiver(mBluetoothScoReceiver); 8715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothScoReceiver = null; 8725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** Enables BT audio using the SCO audio channel. */ 8755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void startBluetoothSco() { 876a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) if (!mHasBluetoothPermission) { 8775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return; 8785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (mBluetoothScoState == STATE_BLUETOOTH_SCO_ON || 8805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothScoState == STATE_BLUETOOTH_SCO_TURNING_ON) { 8815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Unable to turn on BT in this state. 882a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) return; 883a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 884a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 8855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Check if audio is already routed to BT SCO; if so, just update 8865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // states but don't try to enable it again. 8875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (mAudioManager.isBluetoothScoOn()) { 8885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothScoState = STATE_BLUETOOTH_SCO_ON; 8895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return; 890a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 891a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 8925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) logd("startBluetoothSco: turning BT SCO on..."); 8935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothScoState = STATE_BLUETOOTH_SCO_TURNING_ON; 8945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mAudioManager.startBluetoothSco(); 8955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** Disables BT audio using the SCO audio channel. */ 8985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void stopBluetoothSco() { 8995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!mHasBluetoothPermission) { 9005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return; 9015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 9025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (mBluetoothScoState != STATE_BLUETOOTH_SCO_ON && 9035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothScoState != STATE_BLUETOOTH_SCO_TURNING_ON) { 9045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // No need to turn off BT in this state. 9055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return; 906a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 9075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!mAudioManager.isBluetoothScoOn()) { 9085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // TODO(henrika): can we do anything else than logging here? 909f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) loge("Unable to stop BT SCO since it is already disabled"); 9105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return; 9115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 9125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 9135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) logd("stopBluetoothSco: turning BT SCO off..."); 9145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mBluetoothScoState = STATE_BLUETOOTH_SCO_TURNING_OFF; 9155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mAudioManager.stopBluetoothSco(); 916a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 917a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 918a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** 919a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * Changes selection of the currently active audio device. 920a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * 921a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * @param device Specifies the selected audio device. 922a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) */ 9235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void setAudioDevice(int device) { 9245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) logd("setAudioDevice(device=" + device + ")"); 9255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 9265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Ensure that the Bluetooth SCO audio channel is always disabled 9275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // unless the BT headset device is selected. 9285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (device == DEVICE_BLUETOOTH_HEADSET) { 9295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) startBluetoothSco(); 9305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } else { 9315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) stopBluetoothSco(); 9325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 9335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 934a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) switch (device) { 935a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) case DEVICE_BLUETOOTH_HEADSET: 936a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) break; 937a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) case DEVICE_SPEAKERPHONE: 938a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) setSpeakerphoneOn(true); 939a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) break; 940a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) case DEVICE_WIRED_HEADSET: 941a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) setSpeakerphoneOn(false); 942a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) break; 943a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) case DEVICE_EARPIECE: 944a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) setSpeakerphoneOn(false); 945a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) break; 946a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) default: 947f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) loge("Invalid audio device selection"); 948a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) break; 949a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 950a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) reportUpdate(); 951a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 952a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 9535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 9545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Use a special selection scheme if the default device is selected. 9555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * The "most unique" device will be selected; Wired headset first, 9565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * then Bluetooth and last the speaker phone. 9575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 9585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static int selectDefaultDevice(boolean[] devices) { 9595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (devices[DEVICE_WIRED_HEADSET]) { 9605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return DEVICE_WIRED_HEADSET; 9615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } else if (devices[DEVICE_BLUETOOTH_HEADSET]) { 9625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // TODO(henrika): possibly need improvements here if we are 9635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // in a state where Bluetooth is turning off. 9645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return DEVICE_BLUETOOTH_HEADSET; 9655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 9665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return DEVICE_SPEAKERPHONE; 9675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 9685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 9695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** Returns true if setDevice() has been called with a valid device id. */ 9705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private boolean deviceHasBeenRequested() { 9715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) synchronized (mLock) { 9725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return (mRequestedAudioDevice != DEVICE_INVALID); 9735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 9745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 9755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 9765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 9775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Updates the active device given the current list of devices and 9785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * information about if a specific device has been selected or if 9795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * the default device is selected. 9805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 9815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private void updateDeviceActivation() { 9825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) boolean devices[] = null; 9835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int requested = DEVICE_INVALID; 9845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) synchronized (mLock) { 9855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) requested = mRequestedAudioDevice; 9865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) devices = mAudioDevices.clone(); 9875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 9885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (requested == DEVICE_INVALID) { 989f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) loge("Unable to activate device since no device is selected"); 9905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return; 9915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 9925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 9935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Update default device if it has been selected explicitly, or 9945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // the selected device has been removed from the list. 9955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (requested == DEVICE_DEFAULT || !devices[requested]) { 9965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Get default device given current list and activate the device. 9975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int defaultDevice = selectDefaultDevice(devices); 9985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) setAudioDevice(defaultDevice); 9995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } else { 10005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Activate the selected device since we know that it exists in 10015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // the list. 10025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) setAudioDevice(requested); 10035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 10045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 10055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 10065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** Returns number of available devices */ 10075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static int getNumOfAudioDevices(boolean[] devices) { 1008a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) int count = 0; 1009a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) for (int i = 0; i < DEVICE_COUNT; ++i) { 10105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (devices[i]) 10115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ++count; 1012a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 1013a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) return count; 1014a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 1015a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 1016a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** 1017a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * For now, just log the state change but the idea is that we should 1018a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * notify a registered state change listener (if any) that there has 1019a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * been a change in the state. 1020a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) * TODO(henrika): add support for state change listener. 1021a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) */ 1022a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private void reportUpdate() { 1023a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) synchronized (mLock) { 1024a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) List<String> devices = new ArrayList<String>(); 1025a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) for (int i = 0; i < DEVICE_COUNT; ++i) { 1026a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) if (mAudioDevices[i]) 1027a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) devices.add(DEVICE_NAMES[i]); 1028a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 10295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (DEBUG) { 10305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) logd("reportUpdate: requested=" + mRequestedAudioDevice + 10315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ", btSco=" + mBluetoothScoState + 10325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ", devices=" + devices); 10335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1034a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 1035a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 1036a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 1037a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** Information about the current build, taken from system properties. */ 1038a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private void logDeviceInfo() { 1039a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) logd("Android SDK: " + Build.VERSION.SDK_INT + ", " + 1040a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Release: " + Build.VERSION.RELEASE + ", " + 1041a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Brand: " + Build.BRAND + ", " + 1042a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Device: " + Build.DEVICE + ", " + 1043a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Id: " + Build.ID + ", " + 1044a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Hardware: " + Build.HARDWARE + ", " + 1045a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Manufacturer: " + Build.MANUFACTURER + ", " + 1046a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Model: " + Build.MODEL + ", " + 1047a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) "Product: " + Build.PRODUCT); 1048a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 1049a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 1050a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** Trivial helper method for debug logging */ 10515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static void logd(String msg) { 1052a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) Log.d(TAG, msg); 1053a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 1054a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 1055a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) /** Trivial helper method for error logging */ 10565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static void loge(String msg) { 1057a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) Log.e(TAG, msg); 1058a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 1059a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 1060a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** Start thread which observes volume changes on the voice stream. */ 1061a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private void startObservingVolumeChanges() { 1062a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (DEBUG) logd("startObservingVolumeChanges"); 1063a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (mSettingsObserverThread != null) 1064a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return; 1065a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mSettingsObserverThread = new HandlerThread("SettingsObserver"); 1066a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mSettingsObserverThread.start(); 1067a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1068a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mSettingsObserver = new ContentObserver( 1069a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) new Handler(mSettingsObserverThread.getLooper())) { 1070a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1071a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @Override 1072a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) public void onChange(boolean selfChange) { 1073a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (DEBUG) logd("SettingsObserver.onChange: " + selfChange); 1074a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) super.onChange(selfChange); 1075a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1076a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Ensure that the observer is activated during communication mode. 1077a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (mAudioManager.getMode() != AudioManager.MODE_IN_COMMUNICATION) { 10786d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) throw new IllegalStateException( 10796d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) "Only enable SettingsObserver in COMM mode"); 1080a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 1081a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1082a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Get stream volume for the voice stream and deliver callback if 1083a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // the volume index is zero. It is not possible to move the volume 1084a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // slider all the way down in communication mode but the callback 1085a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // implementation can ensure that the volume is completely muted. 1086a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int volume = mAudioManager.getStreamVolume( 1087a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) AudioManager.STREAM_VOICE_CALL); 1088a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (DEBUG) logd("nativeSetMute: " + (volume == 0)); 1089a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) nativeSetMute(mNativeAudioManagerAndroid, (volume == 0)); 1090a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 1091a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) }; 1092a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 1093a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mContentResolver.registerContentObserver( 1094a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) Settings.System.CONTENT_URI, true, mSettingsObserver); 1095a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 1096a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1097a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** Quit observer thread and stop listening for volume changes. */ 1098a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private void stopObservingVolumeChanges() { 1099a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (DEBUG) logd("stopObservingVolumeChanges"); 1100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (mSettingsObserverThread == null) 1101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return; 1102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mContentResolver.unregisterContentObserver(mSettingsObserver); 1104a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mSettingsObserver = null; 1105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1106a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mSettingsObserverThread.quit(); 1107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) try { 1108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mSettingsObserverThread.join(); 1109a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } catch (InterruptedException e) { 11106d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) Log.e(TAG, "Thread.join() exception: ", e); 1111a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 1112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mSettingsObserverThread = null; 1113a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 1114a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 1115a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) private native void nativeSetMute(long nativeAudioManagerAndroid, boolean muted); 11162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1117