• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.bluetooth.btservice;
18 
19 import static android.Manifest.permission.BLUETOOTH_CONNECT;
20 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
21 
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothProfile;
24 import android.content.Intent;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.UserHandle;
29 import android.util.Log;
30 
31 import com.android.bluetooth.Utils;
32 import com.android.bluetooth.a2dp.A2dpService;
33 import com.android.bluetooth.hfp.HeadsetService;
34 
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.List;
38 import java.util.Map;
39 
40 /**
41  * The silence device manager controls silence mode for A2DP, HFP, and AVRCP.
42  *
43  * <p>1) If an active device (for A2DP or HFP) enters silence mode, the active device for that
44  * profile will be set to null. 2) If a device exits silence mode while the A2DP or HFP active
45  * device is null, the device will be set as the active device for that profile. 3) If a device is
46  * disconnected, it exits silence mode. 4) If a device is set as the active device for A2DP or HFP,
47  * while silence mode is enabled, then the device will exit silence mode. 5) If a device is in
48  * silence mode, AVRCP position change event and HFP AG indicators will be disabled. 6) If a device
49  * is not connected with A2DP or HFP, it cannot enter silence mode.
50  */
51 public class SilenceDeviceManager {
52     private static final String TAG = SilenceDeviceManager.class.getSimpleName();
53 
54     private final AdapterService mAdapterService;
55     private final ServiceFactory mFactory;
56     private final Handler mHandler;
57 
58     private final Map<BluetoothDevice, Boolean> mSilenceDevices = new HashMap<>();
59     private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>();
60     private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>();
61 
62     private static final int MSG_SILENCE_DEVICE_STATE_CHANGED = 1;
63     private static final int MSG_A2DP_CONNECTION_STATE_CHANGED = 10;
64     private static final int MSG_HFP_CONNECTION_STATE_CHANGED = 11;
65     private static final int MSG_A2DP_ACTIVE_DEVICE_CHANGED = 20;
66     private static final int MSG_HFP_ACTIVE_DEVICE_CHANGED = 21;
67     private static final int ENABLE_SILENCE = 0;
68     private static final int DISABLE_SILENCE = 1;
69 
70     /**
71      * Called when active state of audio profiles changed
72      *
73      * @param profile The Bluetooth profile of which active state changed
74      * @param device The device currently activated. {@code null} if no device is active
75      */
profileActiveDeviceChanged(int profile, BluetoothDevice device)76     public void profileActiveDeviceChanged(int profile, BluetoothDevice device) {
77         switch (profile) {
78             case BluetoothProfile.A2DP:
79                 mHandler.obtainMessage(MSG_A2DP_ACTIVE_DEVICE_CHANGED, device).sendToTarget();
80                 break;
81             case BluetoothProfile.HEADSET:
82                 mHandler.obtainMessage(MSG_HFP_ACTIVE_DEVICE_CHANGED, device).sendToTarget();
83                 break;
84             default:
85                 break;
86         }
87     }
88 
89     /**
90      * Called when A2DP connection state changed by A2dpService
91      *
92      * @param device The device of which connection state was changed
93      * @param fromState The previous connection state of the device
94      * @param toState The new connection state of the device
95      */
a2dpConnectionStateChanged(BluetoothDevice device, int fromState, int toState)96     public void a2dpConnectionStateChanged(BluetoothDevice device, int fromState, int toState) {
97         mHandler.obtainMessage(MSG_A2DP_CONNECTION_STATE_CHANGED, fromState, toState, device)
98                 .sendToTarget();
99     }
100 
101     /**
102      * Called when HFP connection state changed by HeadsetService
103      *
104      * @param device The device of which connection state was changed
105      * @param fromState The previous connection state of the device
106      * @param toState The new connection state of the device
107      */
hfpConnectionStateChanged(BluetoothDevice device, int fromState, int toState)108     public void hfpConnectionStateChanged(BluetoothDevice device, int fromState, int toState) {
109         mHandler.obtainMessage(MSG_HFP_CONNECTION_STATE_CHANGED, fromState, toState, device)
110                 .sendToTarget();
111     }
112 
113     class SilenceDeviceManagerHandler extends Handler {
114 
SilenceDeviceManagerHandler(Looper looper)115         SilenceDeviceManagerHandler(Looper looper) {
116             super(looper);
117         }
118 
119         @Override
handleMessage(Message msg)120         public void handleMessage(Message msg) {
121             Log.d(TAG, "handleMessage: " + msg.what);
122             switch (msg.what) {
123                 case MSG_SILENCE_DEVICE_STATE_CHANGED:
124                     {
125                         BluetoothDevice device = (BluetoothDevice) msg.obj;
126                         boolean state = (msg.arg1 == ENABLE_SILENCE);
127                         handleSilenceDeviceStateChanged(device, state);
128                     }
129                     break;
130 
131                 case MSG_A2DP_CONNECTION_STATE_CHANGED:
132                     BluetoothDevice device = (BluetoothDevice) msg.obj;
133                     int prevState = msg.arg1;
134                     int nextState = msg.arg2;
135 
136                     if (nextState == STATE_CONNECTED) {
137                         // enter connected state
138                         addConnectedDevice(device, BluetoothProfile.A2DP);
139                         if (!mSilenceDevices.containsKey(device)) {
140                             mSilenceDevices.put(device, false);
141                         }
142                     } else if (prevState == STATE_CONNECTED) {
143                         // exiting from connected state
144                         removeConnectedDevice(device, BluetoothProfile.A2DP);
145                         if (!isBluetoothAudioConnected(device)) {
146                             handleSilenceDeviceStateChanged(device, false);
147                             mSilenceDevices.remove(device);
148                         }
149                     }
150                     break;
151 
152                 case MSG_HFP_CONNECTION_STATE_CHANGED:
153                     BluetoothDevice bluetoothDevice = (BluetoothDevice) msg.obj;
154                     int prev = msg.arg1;
155                     int next = msg.arg2;
156 
157                     if (next == STATE_CONNECTED) {
158                         // enter connected state
159                         addConnectedDevice(bluetoothDevice, BluetoothProfile.HEADSET);
160                         if (!mSilenceDevices.containsKey(bluetoothDevice)) {
161                             mSilenceDevices.put(bluetoothDevice, false);
162                         }
163                     } else if (prev == STATE_CONNECTED) {
164                         // exiting from connected state
165                         removeConnectedDevice(bluetoothDevice, BluetoothProfile.HEADSET);
166                         if (!isBluetoothAudioConnected(bluetoothDevice)) {
167                             handleSilenceDeviceStateChanged(bluetoothDevice, false);
168                             mSilenceDevices.remove(bluetoothDevice);
169                         }
170                     }
171                     break;
172 
173                 case MSG_A2DP_ACTIVE_DEVICE_CHANGED:
174                     BluetoothDevice a2dpActiveDevice = (BluetoothDevice) msg.obj;
175                     if (getSilenceMode(a2dpActiveDevice)) {
176                         // Resume the device from silence mode.
177                         setSilenceMode(a2dpActiveDevice, false);
178                     }
179                     break;
180 
181                 case MSG_HFP_ACTIVE_DEVICE_CHANGED:
182                     BluetoothDevice hfpActiveDevice = (BluetoothDevice) msg.obj;
183                     if (getSilenceMode(hfpActiveDevice)) {
184                         // Resume the device from silence mode.
185                         setSilenceMode(hfpActiveDevice, false);
186                     }
187                     break;
188 
189                 default:
190                     Log.e(TAG, "Unknown message: " + msg.what);
191                     break;
192             }
193         }
194     }
195 
SilenceDeviceManager(AdapterService service, ServiceFactory factory, Looper looper)196     SilenceDeviceManager(AdapterService service, ServiceFactory factory, Looper looper) {
197         mAdapterService = service;
198         mFactory = factory;
199         mHandler = new SilenceDeviceManagerHandler(looper);
200     }
201 
cleanup()202     void cleanup() {
203         Log.v(TAG, "cleanup()");
204         mSilenceDevices.clear();
205     }
206 
setSilenceMode(BluetoothDevice device, boolean silence)207     boolean setSilenceMode(BluetoothDevice device, boolean silence) {
208         Log.d(TAG, "setSilenceMode: " + device + ", " + silence);
209         mHandler.obtainMessage(
210                         MSG_SILENCE_DEVICE_STATE_CHANGED,
211                         silence ? ENABLE_SILENCE : DISABLE_SILENCE,
212                         0,
213                         device)
214                 .sendToTarget();
215         return true;
216     }
217 
handleSilenceDeviceStateChanged(BluetoothDevice device, boolean state)218     void handleSilenceDeviceStateChanged(BluetoothDevice device, boolean state) {
219         boolean oldState = getSilenceMode(device);
220         if (oldState == state) {
221             return;
222         }
223         if (!isBluetoothAudioConnected(device)) {
224             if (oldState) {
225                 // Device is disconnected, resume all silenced profiles.
226                 state = false;
227             } else {
228                 Log.d(TAG, "Device is not connected to any Bluetooth audio.");
229                 return;
230             }
231         }
232         mSilenceDevices.replace(device, state);
233 
234         A2dpService a2dpService = mFactory.getA2dpService();
235         if (a2dpService != null) {
236             a2dpService.setSilenceMode(device, state);
237         }
238         HeadsetService headsetService = mFactory.getHeadsetService();
239         if (headsetService != null) {
240             headsetService.setSilenceMode(device, state);
241         }
242         Log.i(TAG, "Silence mode change " + device + ": " + oldState + " -> " + state);
243         broadcastSilenceStateChange(device);
244     }
245 
broadcastSilenceStateChange(BluetoothDevice device)246     private void broadcastSilenceStateChange(BluetoothDevice device) {
247         Intent intent = new Intent(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED);
248         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
249         mAdapterService.sendBroadcastAsUser(
250                 intent,
251                 UserHandle.ALL,
252                 BLUETOOTH_CONNECT,
253                 Utils.getTempBroadcastOptions().toBundle());
254     }
255 
getSilenceMode(BluetoothDevice device)256     boolean getSilenceMode(BluetoothDevice device) {
257         boolean state = false;
258         if (mSilenceDevices.containsKey(device)) {
259             state = mSilenceDevices.get(device);
260         }
261         return state;
262     }
263 
addConnectedDevice(BluetoothDevice device, int profile)264     private void addConnectedDevice(BluetoothDevice device, int profile) {
265         Log.d(
266                 TAG,
267                 "addConnectedDevice: "
268                         + device
269                         + ", profile:"
270                         + BluetoothProfile.getProfileName(profile));
271         switch (profile) {
272             case BluetoothProfile.A2DP:
273                 if (!mA2dpConnectedDevices.contains(device)) {
274                     mA2dpConnectedDevices.add(device);
275                 }
276                 break;
277             case BluetoothProfile.HEADSET:
278                 if (!mHfpConnectedDevices.contains(device)) {
279                     mHfpConnectedDevices.add(device);
280                 }
281                 break;
282         }
283     }
284 
removeConnectedDevice(BluetoothDevice device, int profile)285     private void removeConnectedDevice(BluetoothDevice device, int profile) {
286         Log.d(
287                 TAG,
288                 "removeConnectedDevice: "
289                         + device
290                         + ", profile:"
291                         + BluetoothProfile.getProfileName(profile));
292         switch (profile) {
293             case BluetoothProfile.A2DP:
294                 if (mA2dpConnectedDevices.contains(device)) {
295                     mA2dpConnectedDevices.remove(device);
296                 }
297                 break;
298             case BluetoothProfile.HEADSET:
299                 if (mHfpConnectedDevices.contains(device)) {
300                     mHfpConnectedDevices.remove(device);
301                 }
302                 break;
303         }
304     }
305 
isBluetoothAudioConnected(BluetoothDevice device)306     private boolean isBluetoothAudioConnected(BluetoothDevice device) {
307         return (mA2dpConnectedDevices.contains(device) || mHfpConnectedDevices.contains(device));
308     }
309 
dump(StringBuilder sb)310     protected void dump(StringBuilder sb) {
311         sb.append("SilenceDeviceManager:\n");
312         sb.append("  Address            | Is silenced?\n");
313         for (BluetoothDevice device : mSilenceDevices.keySet()) {
314             sb.append("  ")
315                     .append(device)
316                     .append("  | ")
317                     .append(getSilenceMode(device))
318                     .append("\n");
319         }
320     }
321 }
322