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