1 /*
2  * Copyright (C) 2018 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.bluetooth;
18 
19 import android.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothHeadset;
21 import android.bluetooth.BluetoothHearingAid;
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.telecom.Log;
28 import android.telecom.Logging.Session;
29 
30 import com.android.internal.os.SomeArgs;
31 
32 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON;
33 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST;
34 
35 
36 public class BluetoothStateReceiver extends BroadcastReceiver {
37     private static final String LOG_TAG = BluetoothStateReceiver.class.getSimpleName();
38     public static final IntentFilter INTENT_FILTER;
39     static {
40         INTENT_FILTER = new IntentFilter();
41         INTENT_FILTER.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
42         INTENT_FILTER.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
43         INTENT_FILTER.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
44         INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
45         INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
46     }
47 
48     // If not in a call, BSR won't listen to the Bluetooth stack's HFP on/off messages, since
49     // other apps could be turning it on and off. We don't want to interfere.
50     private boolean mIsInCall = false;
51     private final BluetoothRouteManager mBluetoothRouteManager;
52     private final BluetoothDeviceManager mBluetoothDeviceManager;
53 
onReceive(Context context, Intent intent)54     public void onReceive(Context context, Intent intent) {
55         Log.startSession("BSR.oR");
56         try {
57             String action = intent.getAction();
58             switch (action) {
59                 case BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED:
60                     handleAudioStateChanged(intent);
61                     break;
62                 case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
63                 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
64                     handleConnectionStateChanged(intent);
65                     break;
66                 case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
67                 case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
68                     handleActiveDeviceChanged(intent);
69                     break;
70             }
71         } finally {
72             Log.endSession();
73         }
74     }
75 
handleAudioStateChanged(Intent intent)76     private void handleAudioStateChanged(Intent intent) {
77         int bluetoothHeadsetAudioState =
78                 intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
79                         BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
80         BluetoothDevice device =
81                 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
82         if (device == null) {
83             Log.w(LOG_TAG, "Got null device from broadcast. " +
84                     "Ignoring.");
85             return;
86         }
87 
88         Log.i(LOG_TAG, "Device %s transitioned to audio state %d",
89                 device.getAddress(), bluetoothHeadsetAudioState);
90         Session session = Log.createSubsession();
91         SomeArgs args = SomeArgs.obtain();
92         args.arg1 = session;
93         args.arg2 = device.getAddress();
94         switch (bluetoothHeadsetAudioState) {
95             case BluetoothHeadset.STATE_AUDIO_CONNECTED:
96                 if (!mIsInCall) {
97                     Log.i(LOG_TAG, "Ignoring BT audio on since we're not in a call");
98                     return;
99                 }
100                 mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
101                 break;
102             case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
103                 mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
104                 break;
105         }
106     }
107 
handleConnectionStateChanged(Intent intent)108     private void handleConnectionStateChanged(Intent intent) {
109         int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
110                 BluetoothHeadset.STATE_DISCONNECTED);
111         BluetoothDevice device =
112                 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
113 
114         if (device == null) {
115             Log.w(LOG_TAG, "Got null device from broadcast. " +
116                     "Ignoring.");
117             return;
118         }
119 
120         Log.i(LOG_TAG, "Device %s changed state to %d",
121                 device.getAddress(), bluetoothHeadsetState);
122 
123         boolean isHearingAid = BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED
124                 .equals(intent.getAction());
125         if (bluetoothHeadsetState == BluetoothProfile.STATE_CONNECTED) {
126             mBluetoothDeviceManager.onDeviceConnected(device, isHearingAid);
127         } else if (bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTED
128                 || bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTING) {
129             mBluetoothDeviceManager.onDeviceDisconnected(device, isHearingAid);
130         }
131     }
132 
handleActiveDeviceChanged(Intent intent)133     private void handleActiveDeviceChanged(Intent intent) {
134         BluetoothDevice device =
135                 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
136         boolean isHearingAid =
137                 BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction());
138         Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device,
139                 isHearingAid ? "hearing aid" : "HFP");
140 
141         mBluetoothRouteManager.onActiveDeviceChanged(device, isHearingAid);
142         if (isHearingAid) {
143             Session session = Log.createSubsession();
144             SomeArgs args = SomeArgs.obtain();
145             args.arg1 = session;
146             if (device == null) {
147                 mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
148             } else {
149                 if (!mIsInCall) {
150                     Log.i(LOG_TAG, "Ignoring hearing aid audio on since we're not in a call");
151                     return;
152                 }
153                 args.arg2 = device.getAddress();
154                 mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
155             }
156         }
157     }
158 
getBluetoothDeviceManager()159     public BluetoothDeviceManager getBluetoothDeviceManager() {
160         return mBluetoothDeviceManager;
161     }
162 
BluetoothStateReceiver(BluetoothDeviceManager deviceManager, BluetoothRouteManager routeManager)163     public BluetoothStateReceiver(BluetoothDeviceManager deviceManager,
164             BluetoothRouteManager routeManager) {
165         mBluetoothDeviceManager = deviceManager;
166         mBluetoothRouteManager = routeManager;
167     }
168 
setIsInCall(boolean isInCall)169     public void setIsInCall(boolean isInCall) {
170         mIsInCall = isInCall;
171     }
172 }
173