blob: efdb724440ac1b078c8e7171145ea76c4e27f8df [file] [log] [blame]
Santos Cordonc7e85d42014-05-22 02:51:48 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.telecomm;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothHeadset;
22import android.bluetooth.BluetoothProfile;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.os.SystemClock;
28
29import java.util.ArrayList;
30import java.util.List;
31
32/**
33 * Listens to and caches bluetooth headset state. Used By the CallAudioManager for maintaining
34 * overall audio state. Also provides method for connecting the bluetooth headset to the phone call.
35 */
36public class BluetoothManager {
37
38 private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
39 new BluetoothProfile.ServiceListener() {
40 @Override
41 public void onServiceConnected(int profile, BluetoothProfile proxy) {
42 mBluetoothHeadset = (BluetoothHeadset) proxy;
43 Log.v(this, "- Got BluetoothHeadset: " + mBluetoothHeadset);
44 }
45
46 @Override
47 public void onServiceDisconnected(int profile) {
48 mBluetoothHeadset = null;
49 }
50 };
51
52 /**
53 * Receiver for misc intent broadcasts the BluetoothManager cares about.
54 */
55 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
56 @Override
57 public void onReceive(Context context, Intent intent) {
58 String action = intent.getAction();
59
60 if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
61 int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
62 BluetoothHeadset.STATE_DISCONNECTED);
63 Log.d(this, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
64 Log.d(this, "==> new state: %s ", bluetoothHeadsetState);
65 updateBluetoothState();
66 } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
67 int bluetoothHeadsetAudioState =
68 intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
69 BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
70 Log.d(this, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
71 Log.d(this, "==> new state: %s", bluetoothHeadsetAudioState);
72 updateBluetoothState();
73 }
74 }
75 };
76
77 private final BluetoothAdapter mBluetoothAdapter;
78 private final CallAudioManager mCallAudioManager;
79
80 private BluetoothHeadset mBluetoothHeadset;
81 private boolean mBluetoothConnectionPending = false;
82 private long mBluetoothConnectionRequestTime;
83
84
85 public BluetoothManager(Context context, CallAudioManager callAudioManager) {
86 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
87 mCallAudioManager = callAudioManager;
88
89 if (mBluetoothAdapter != null) {
90 mBluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
91 BluetoothProfile.HEADSET);
92 }
93
94 // Register for misc other intent broadcasts.
95 IntentFilter intentFilter =
96 new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
97 intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
98 context.registerReceiver(mReceiver, intentFilter);
99 }
100
101 //
102 // Bluetooth helper methods.
103 //
104 // - BluetoothAdapter is the Bluetooth system service. If
105 // getDefaultAdapter() returns null
106 // then the device is not BT capable. Use BluetoothDevice.isEnabled()
107 // to see if BT is enabled on the device.
108 //
109 // - BluetoothHeadset is the API for the control connection to a
110 // Bluetooth Headset. This lets you completely connect/disconnect a
111 // headset (which we don't do from the Phone UI!) but also lets you
112 // get the address of the currently active headset and see whether
113 // it's currently connected.
114
115 /**
116 * @return true if the Bluetooth on/off switch in the UI should be
117 * available to the user (i.e. if the device is BT-capable
118 * and a headset is connected.)
119 */
120 boolean isBluetoothAvailable() {
121 Log.v(this, "isBluetoothAvailable()...");
122
123 // There's no need to ask the Bluetooth system service if BT is enabled:
124 //
125 // BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
126 // if ((adapter == null) || !adapter.isEnabled()) {
127 // Log.d(this, " ==> FALSE (BT not enabled)");
128 // return false;
129 // }
130 // Log.d(this, " - BT enabled! device name " + adapter.getName()
131 // + ", address " + adapter.getAddress());
132 //
133 // ...since we already have a BluetoothHeadset instance. We can just
134 // call isConnected() on that, and assume it'll be false if BT isn't
135 // enabled at all.
136
137 // Check if there's a connected headset, using the BluetoothHeadset API.
138 boolean isConnected = false;
139 if (mBluetoothHeadset != null) {
140 List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
141
142 if (deviceList.size() > 0) {
143 BluetoothDevice device = deviceList.get(0);
144 isConnected = true;
145
146 Log.v(this, " - headset state = " + mBluetoothHeadset.getConnectionState(device));
147 Log.v(this, " - headset address: " + device);
148 Log.v(this, " - isConnected: " + isConnected);
149 }
150 }
151
152 Log.v(this, " ==> " + isConnected);
153 return isConnected;
154 }
155
156 /**
157 * @return true if a BT Headset is available, and its audio is currently connected.
158 */
159 boolean isBluetoothAudioConnected() {
160 if (mBluetoothHeadset == null) {
161 Log.v(this, "isBluetoothAudioConnected: ==> FALSE (null mBluetoothHeadset)");
162 return false;
163 }
164 List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
165
166 if (deviceList.isEmpty()) {
167 return false;
168 }
169 BluetoothDevice device = deviceList.get(0);
170 boolean isAudioOn = mBluetoothHeadset.isAudioConnected(device);
171 Log.v(this, "isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn);
172 return isAudioOn;
173 }
174
175 /**
176 * Helper method used to control the onscreen "Bluetooth" indication;
177 *
178 * @return true if a BT device is available and its audio is currently connected,
179 * <b>or</b> if we issued a BluetoothHeadset.connectAudio()
180 * call within the last 5 seconds (which presumably means
181 * that the BT audio connection is currently being set
182 * up, and will be connected soon.)
183 */
184 /* package */ boolean isBluetoothAudioConnectedOrPending() {
185 if (isBluetoothAudioConnected()) {
186 Log.v(this, "isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)");
187 return true;
188 }
189
190 // If we issued a connectAudio() call "recently enough", even
191 // if BT isn't actually connected yet, let's still pretend BT is
192 // on. This makes the onscreen indication more responsive.
193 if (mBluetoothConnectionPending) {
194 long timeSinceRequest =
195 SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime;
196 if (timeSinceRequest < 5000 /* 5 seconds */) {
197 Log.v(this, "isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
198 + timeSinceRequest + " msec ago)");
199 return true;
200 } else {
201 Log.v(this, "isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: "
202 + timeSinceRequest + " msec ago)");
203 mBluetoothConnectionPending = false;
204 return false;
205 }
206 }
207
208 Log.v(this, "isBluetoothAudioConnectedOrPending: ==> FALSE");
209 return false;
210 }
211
212 /**
213 * Notified audio manager of a change to the bluetooth state.
214 */
215 void updateBluetoothState() {
216 mCallAudioManager.onBluetoothStateChange(this);
217 }
218
219 void connectBluetoothAudio() {
220 Log.v(this, "connectBluetoothAudio()...");
221 if (mBluetoothHeadset != null) {
222 mBluetoothHeadset.connectAudio();
223 }
224
225 // Watch out: The bluetooth connection doesn't happen instantly;
226 // the connectAudio() call returns instantly but does its real
227 // work in another thread. The mBluetoothConnectionPending flag
228 // is just a little trickery to ensure that the onscreen UI updates
229 // instantly. (See isBluetoothAudioConnectedOrPending() above.)
230 mBluetoothConnectionPending = true;
231 mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
232 }
233
234 void disconnectBluetoothAudio() {
235 Log.v(this, "disconnectBluetoothAudio()...");
236 if (mBluetoothHeadset != null) {
237 mBluetoothHeadset.disconnectAudio();
238 }
239 mBluetoothConnectionPending = false;
240 }
241
242 private void dumpBluetoothState() {
243 Log.d(this, "============== dumpBluetoothState() =============");
244 Log.d(this, "= isBluetoothAvailable: " + isBluetoothAvailable());
245 Log.d(this, "= isBluetoothAudioConnected: " + isBluetoothAudioConnected());
246 Log.d(this, "= isBluetoothAudioConnectedOrPending: " +
247 isBluetoothAudioConnectedOrPending());
248 Log.d(this, "=");
249 if (mBluetoothAdapter != null) {
250 if (mBluetoothHeadset != null) {
251 List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
252
253 if (deviceList.size() > 0) {
254 BluetoothDevice device = deviceList.get(0);
255 Log.d(this, "= BluetoothHeadset.getCurrentDevice: " + device);
256 Log.d(this, "= BluetoothHeadset.State: "
257 + mBluetoothHeadset.getConnectionState(device));
258 Log.d(this, "= BluetoothHeadset audio connected: " +
259 mBluetoothHeadset.isAudioConnected(device));
260 }
261 } else {
262 Log.d(this, "= mBluetoothHeadset is null");
263 }
264 } else {
265 Log.d(this, "= mBluetoothAdapter is null; device is not BT capable");
266 }
267 }
268}