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