1 /*
2  * Copyright (C) 2021 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.car.bluetooth;
18 
19 import android.bluetooth.BluetoothA2dp;
20 import android.bluetooth.BluetoothA2dpSink;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothHeadsetClient;
24 import android.bluetooth.BluetoothMapClient;
25 import android.bluetooth.BluetoothPan;
26 import android.bluetooth.BluetoothPbapClient;
27 import android.bluetooth.BluetoothProfile;
28 import android.bluetooth.BluetoothUuid;
29 import android.bluetooth.le.AdvertisingSetCallback;
30 import android.os.ParcelUuid;
31 import android.util.SparseArray;
32 
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 
37 /** Utils for Bluetooth */
38 public final class BluetoothUtils {
BluetoothUtils()39     private BluetoothUtils() {
40         throw new UnsupportedOperationException();
41     }
42 
43     public static final String A2DP_SOURCE_CONNECTION_STATE_CHANGED =
44             BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED;
45     public static final String A2DP_SINK_CONNECTION_STATE_CHANGED =
46             BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED;
47     public static final String HFP_CLIENT_CONNECTION_STATE_CHANGED =
48             BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED;
49     public static final String MAP_CLIENT_CONNECTION_STATE_CHANGED =
50             BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED;
51     public static final String PAN_CONNECTION_STATE_CHANGED =
52             BluetoothPan.ACTION_CONNECTION_STATE_CHANGED;
53     public static final String PBAP_CLIENT_CONNECTION_STATE_CHANGED =
54             BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED;
55 
56     private static final ParcelUuid[] A2DP_SOURCE_UUIDS =
57             new ParcelUuid[]{BluetoothUuid.A2DP_SOURCE};
58     private static final ParcelUuid[] A2DP_SINK_UUIDS =
59             new ParcelUuid[]{BluetoothUuid.A2DP_SINK};
60     private static final ParcelUuid[] HFP_HF_UUIDS =
61             new ParcelUuid[]{BluetoothUuid.HFP};
62     private static final ParcelUuid[] HFP_AG_UUIDS =
63             new ParcelUuid[]{BluetoothUuid.HFP_AG, BluetoothUuid.HSP_AG};
64     private static final ParcelUuid[] MAP_CLIENT_UUIDS =
65             new ParcelUuid[]{BluetoothUuid.MAP, BluetoothUuid.MNS};
66     private static final ParcelUuid[] MAP_SERVER_UUIDS =
67             new ParcelUuid[]{BluetoothUuid.MAS};
68     private static final ParcelUuid[] PAN_UUIDS =
69             new ParcelUuid[]{BluetoothUuid.PANU, BluetoothUuid.NAP};
70     private static final ParcelUuid[] PBAP_CLIENT_UUIDS =
71             new ParcelUuid[]{BluetoothUuid.PBAP_PCE};
72     private static final ParcelUuid[] PBAP_SERVER_UUIDS =
73             new ParcelUuid[]{BluetoothUuid.PBAP_PSE};
74 
75     /*
76      * Maps of types and status to human readable strings
77      */
78 
79     private static final SparseArray<String> sAdapterStates = new SparseArray<String>(4);
80     private static final SparseArray<String> sBondStates = new SparseArray<String>(3);
81     private static final SparseArray<String> sConnectionStates = new SparseArray<String>(4);
82     private static final SparseArray<String> sScanModes = new SparseArray<String>(3);
83     private static final SparseArray<String> sAdvertiseCallbackStatuses =
84             new SparseArray<String>(3);
85     private static final SparseArray<String> sProfileNames = new SparseArray<String>(6);
86     private static final HashMap<String, Integer> sProfileActions = new HashMap<String, Integer>(5);
87     static {
88         // Bluetooth Adapter states
sAdapterStates.put(BluetoothAdapter.STATE_ON, "On")89         sAdapterStates.put(BluetoothAdapter.STATE_ON, "On");
sAdapterStates.put(BluetoothAdapter.STATE_OFF, "Off")90         sAdapterStates.put(BluetoothAdapter.STATE_OFF, "Off");
sAdapterStates.put(BluetoothAdapter.STATE_TURNING_ON, "Turning On")91         sAdapterStates.put(BluetoothAdapter.STATE_TURNING_ON, "Turning On");
sAdapterStates.put(BluetoothAdapter.STATE_TURNING_OFF, "Turning Off")92         sAdapterStates.put(BluetoothAdapter.STATE_TURNING_OFF, "Turning Off");
sAdapterStates.put(BluetoothAdapter.ERROR, "Error")93         sAdapterStates.put(BluetoothAdapter.ERROR, "Error");
94 
95         // Device Bonding states
sBondStates.put(BluetoothDevice.BOND_BONDED, "Bonded")96         sBondStates.put(BluetoothDevice.BOND_BONDED, "Bonded");
sBondStates.put(BluetoothDevice.BOND_BONDING, "Bonding")97         sBondStates.put(BluetoothDevice.BOND_BONDING, "Bonding");
sBondStates.put(BluetoothDevice.BOND_NONE, "Unbonded")98         sBondStates.put(BluetoothDevice.BOND_NONE, "Unbonded");
99 
100         // Device and Profile Connection states
sConnectionStates.put(BluetoothAdapter.STATE_CONNECTED, "Connected")101         sConnectionStates.put(BluetoothAdapter.STATE_CONNECTED, "Connected");
sConnectionStates.put(BluetoothAdapter.STATE_DISCONNECTED, "Disconnected")102         sConnectionStates.put(BluetoothAdapter.STATE_DISCONNECTED, "Disconnected");
sConnectionStates.put(BluetoothAdapter.STATE_CONNECTING, "Connecting")103         sConnectionStates.put(BluetoothAdapter.STATE_CONNECTING, "Connecting");
sConnectionStates.put(BluetoothAdapter.STATE_DISCONNECTING, "Disconnecting")104         sConnectionStates.put(BluetoothAdapter.STATE_DISCONNECTING, "Disconnecting");
105 
106         // Scan Mode Names
sScanModes.put(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, "Connectable/Discoverable")107         sScanModes.put(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
108                 "Connectable/Discoverable");
sScanModes.put(BluetoothAdapter.SCAN_MODE_CONNECTABLE, "Connectable")109         sScanModes.put(BluetoothAdapter.SCAN_MODE_CONNECTABLE, "Connectable");
sScanModes.put(BluetoothAdapter.SCAN_MODE_NONE, "None")110         sScanModes.put(BluetoothAdapter.SCAN_MODE_NONE, "None");
sScanModes.put(BluetoothAdapter.ERROR, "Error")111         sScanModes.put(BluetoothAdapter.ERROR, "Error");
112 
113         // Advertising Callback Status Codes
sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_FAILED_ALREADY_STARTED, "ADVERTISE_FAILED_ALREADY_STARTED")114         sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_FAILED_ALREADY_STARTED,
115                 "ADVERTISE_FAILED_ALREADY_STARTED");
sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_FAILED_DATA_TOO_LARGE, "ADVERTISE_FAILED_DATA_TOO_LARGE")116         sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_FAILED_DATA_TOO_LARGE,
117                 "ADVERTISE_FAILED_DATA_TOO_LARGE");
sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED, "ADVERTISE_FAILED_FEATURE_UNSUPPORTED")118         sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED,
119                 "ADVERTISE_FAILED_FEATURE_UNSUPPORTED");
sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_FAILED_INTERNAL_ERROR, "ADVERTISE_FAILED_INTERNAL_ERROR")120         sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_FAILED_INTERNAL_ERROR,
121                 "ADVERTISE_FAILED_INTERNAL_ERROR");
sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS, "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS")122         sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS,
123                 "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS");
sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_SUCCESS, "ADVERTISE_SUCCESS")124         sAdvertiseCallbackStatuses.put(AdvertisingSetCallback.ADVERTISE_SUCCESS,
125                 "ADVERTISE_SUCCESS");
126 
127         // Profile Names
sProfileNames.put(BluetoothProfile.PAN, "PAN")128         sProfileNames.put(BluetoothProfile.PAN, "PAN");
sProfileNames.put(BluetoothProfile.A2DP, "A2DP Source")129         sProfileNames.put(BluetoothProfile.A2DP, "A2DP Source");
sProfileNames.put(BluetoothProfile.A2DP_SINK, "A2DP Sink")130         sProfileNames.put(BluetoothProfile.A2DP_SINK, "A2DP Sink");
sProfileNames.put(BluetoothProfile.AVRCP_CONTROLLER, "AVRCP Controller")131         sProfileNames.put(BluetoothProfile.AVRCP_CONTROLLER, "AVRCP Controller");
sProfileNames.put(BluetoothProfile.HEADSET_CLIENT, "HFP Client")132         sProfileNames.put(BluetoothProfile.HEADSET_CLIENT, "HFP Client");
sProfileNames.put(BluetoothProfile.PBAP_CLIENT, "PBAP Client")133         sProfileNames.put(BluetoothProfile.PBAP_CLIENT, "PBAP Client");
sProfileNames.put(BluetoothProfile.MAP_CLIENT, "MAP Client")134         sProfileNames.put(BluetoothProfile.MAP_CLIENT, "MAP Client");
135 
136         // Profile actions to ints
sProfileActions.put(A2DP_SOURCE_CONNECTION_STATE_CHANGED, BluetoothProfile.A2DP)137         sProfileActions.put(A2DP_SOURCE_CONNECTION_STATE_CHANGED, BluetoothProfile.A2DP);
sProfileActions.put(A2DP_SINK_CONNECTION_STATE_CHANGED, BluetoothProfile.A2DP_SINK)138         sProfileActions.put(A2DP_SINK_CONNECTION_STATE_CHANGED, BluetoothProfile.A2DP_SINK);
sProfileActions.put(HFP_CLIENT_CONNECTION_STATE_CHANGED, BluetoothProfile.HEADSET_CLIENT)139         sProfileActions.put(HFP_CLIENT_CONNECTION_STATE_CHANGED, BluetoothProfile.HEADSET_CLIENT);
sProfileActions.put(MAP_CLIENT_CONNECTION_STATE_CHANGED, BluetoothProfile.MAP_CLIENT)140         sProfileActions.put(MAP_CLIENT_CONNECTION_STATE_CHANGED, BluetoothProfile.MAP_CLIENT);
sProfileActions.put(PAN_CONNECTION_STATE_CHANGED, BluetoothProfile.PAN)141         sProfileActions.put(PAN_CONNECTION_STATE_CHANGED, BluetoothProfile.PAN);
sProfileActions.put(PBAP_CLIENT_CONNECTION_STATE_CHANGED, BluetoothProfile.PBAP_CLIENT)142         sProfileActions.put(PBAP_CLIENT_CONNECTION_STATE_CHANGED, BluetoothProfile.PBAP_CLIENT);
143     }
144 
getBytesFromAddress(String address)145     static byte[] getBytesFromAddress(String address) {
146         int i, j = 0;
147         byte[] output = new byte[6]; // 6 byte Bluetooth Address
148 
149         for (i = 0; i < address.length(); i++) {
150             if (address.charAt(i) != ':') {
151                 output[j] = (byte) Integer.parseInt(address.substring(i, i + 2), 16 /* base 16 */);
152                 j++;
153                 i++;
154             }
155         }
156         return output;
157     }
158 
159 
getDeviceDebugInfo(BluetoothDevice device)160     static String getDeviceDebugInfo(BluetoothDevice device) {
161         if (device == null) {
162             return "(null)";
163         }
164         return "(name = " + device.getName() + ", addr = " + device.getAddress() + ")";
165     }
166 
getProfileName(int profile)167     static String getProfileName(int profile) {
168         String name = sProfileNames.get(profile, "Unknown");
169         return "(" + profile + ") " + name;
170     }
171 
getConnectionStateName(int state)172     static String getConnectionStateName(int state) {
173         String name = sConnectionStates.get(state, "Unknown");
174         return "(" + state + ") " + name;
175     }
176 
getBondStateName(int state)177     static String getBondStateName(int state) {
178         String name = sBondStates.get(state, "Unknown");
179         return "(" + state + ") " + name;
180     }
181 
getAdapterStateName(int state)182     static String getAdapterStateName(int state) {
183         String name = sAdapterStates.get(state, "Unknown");
184         return "(" + state + ") " + name;
185     }
186 
getScanModeName(int mode)187     static String getScanModeName(int mode) {
188         String name = sScanModes.get(mode, "Unknown");
189         return "(" + mode + ") " + name;
190     }
191 
getAdvertisingCallbackStatusName(int status)192     static String getAdvertisingCallbackStatusName(int status) {
193         String name = sAdvertiseCallbackStatuses.get(status, "Unknown");
194         return "(" + status + ") " + name;
195     }
196 
getConnectionPolicyName(int priority)197     static String getConnectionPolicyName(int priority) {
198         String name = "";
199         switch (priority) {
200             case BluetoothProfile.CONNECTION_POLICY_ALLOWED:
201                 name = "CONNECTION_POLICY_ALLOWED";
202                 break;
203             case BluetoothProfile.CONNECTION_POLICY_FORBIDDEN:
204                 name = "CONNECTION_POLICY_FORBIDDEN";
205                 break;
206             case BluetoothProfile.CONNECTION_POLICY_UNKNOWN:
207                 name = "CONNECTION_POLICY_UNKNOWN";
208                 break;
209             default:
210                 name = "Unknown";
211                 break;
212         }
213         return "(" + priority + ") " + name;
214     }
215 
getProfileFromConnectionAction(String action)216     static int getProfileFromConnectionAction(String action) {
217         Integer profile = sProfileActions.get(action);
218         return profile != null ? profile.intValue() : -1;
219     }
220 
isProfileSupported(List<ParcelUuid> localUuids, BluetoothDevice device, int profile)221     static boolean isProfileSupported(List<ParcelUuid> localUuids, BluetoothDevice device,
222             int profile) {
223         if (device == null || localUuids == null || localUuids.isEmpty()) {
224             return false;
225         }
226 
227         ParcelUuid[] ourUuids = localUuids.toArray(new ParcelUuid[localUuids.size()]);
228         ParcelUuid[] uuids = device.getUuids();
229 
230         if (uuids == null || uuids.length == 0) {
231             return false;
232         }
233         switch (profile) {
234             case BluetoothProfile.A2DP:
235                 return BluetoothUuid.containsAnyUuid(ourUuids, A2DP_SOURCE_UUIDS)
236                         && BluetoothUuid.containsAnyUuid(uuids, A2DP_SINK_UUIDS);
237             case BluetoothProfile.A2DP_SINK:
238                 return BluetoothUuid.containsAnyUuid(ourUuids, A2DP_SINK_UUIDS)
239                         && BluetoothUuid.containsAnyUuid(uuids, A2DP_SOURCE_UUIDS);
240             case BluetoothProfile.HEADSET_CLIENT:
241                 return BluetoothUuid.containsAnyUuid(ourUuids, HFP_HF_UUIDS)
242                         && BluetoothUuid.containsAnyUuid(uuids, HFP_AG_UUIDS);
243             case BluetoothProfile.MAP_CLIENT:
244                 return BluetoothUuid.containsAnyUuid(ourUuids, MAP_CLIENT_UUIDS)
245                         && BluetoothUuid.containsAnyUuid(uuids, MAP_SERVER_UUIDS);
246             case BluetoothProfile.PAN:
247                 return BluetoothUuid.containsAnyUuid(ourUuids, PAN_UUIDS)
248                         && BluetoothUuid.containsAnyUuid(uuids, PAN_UUIDS);
249             case BluetoothProfile.PBAP_CLIENT:
250                 return BluetoothUuid.containsAnyUuid(ourUuids, PBAP_CLIENT_UUIDS)
251                         && BluetoothUuid.containsAnyUuid(uuids, PBAP_SERVER_UUIDS);
252             default:
253                 return false;
254         }
255     }
256 
isAProfileAction(String action)257     static boolean isAProfileAction(String action) {
258         return sProfileActions.containsKey(action);
259     }
260 
getManagedProfilesIds()261     static int[] getManagedProfilesIds() {
262         int[] profileIds = new int[sProfileActions.size()];
263         int i = 0;
264         for (Map.Entry<String, Integer> record : sProfileActions.entrySet()) {
265             profileIds[i] = ((Integer) record.getValue()).intValue();
266             i += 1;
267         }
268         return profileIds;
269     }
270 }
271