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