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