1/* 2 * Copyright (C) 2017 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 */ 16package com.android.car; 17 18import static android.car.settings.CarSettings.Secure 19 .KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICE_PRIORITY_0; 20import static android.car.settings.CarSettings.Secure 21 .KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICE_PRIORITY_1; 22import static android.car.settings.CarSettings.Secure 23 .KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICE_PRIORITY_0; 24import static android.car.settings.CarSettings.Secure 25 .KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICE_PRIORITY_1; 26import static android.car.settings.CarSettings.Secure 27 .KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICE_PRIORITY_0; 28import static android.car.settings.CarSettings.Secure 29 .KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICE_PRIORITY_1; 30 31import android.app.ActivityManager; 32import android.bluetooth.BluetoothDevice; 33import android.bluetooth.BluetoothProfile; 34import android.car.CarBluetoothManager; 35import android.car.ICarBluetooth; 36import android.content.Context; 37import android.content.pm.PackageManager; 38import android.provider.Settings; 39import android.util.Log; 40 41import java.io.PrintWriter; 42 43/** 44 * CarBluetoothService - deals with the automatically connecting to a known device via bluetooth. 45 * Interacts with a policy -{@link BluetoothDeviceConnectionPolicy} -to initiate connections and 46 * update status. 47 * The {@link BluetoothDeviceConnectionPolicy} is responsible for finding the appropriate device to 48 * connect for a specific profile. 49 */ 50 51public class CarBluetoothService extends ICarBluetooth.Stub implements CarServiceBase { 52 53 private static final String TAG = "CarBluetoothService"; 54 private final Context mContext; 55 private final BluetoothDeviceConnectionPolicy mBluetoothDeviceConnectionPolicy; 56 private static final boolean DBG = false; 57 58 public CarBluetoothService(Context context, CarCabinService carCabinService, 59 CarSensorService carSensorService, PerUserCarServiceHelper userSwitchService) { 60 mContext = context; 61 mBluetoothDeviceConnectionPolicy = BluetoothDeviceConnectionPolicy.create(mContext, 62 carCabinService, carSensorService, userSwitchService, this); 63 } 64 65 @Override 66 public void init() { 67 mBluetoothDeviceConnectionPolicy.init(); 68 } 69 70 @Override 71 public synchronized void release() { 72 mBluetoothDeviceConnectionPolicy.release(); 73 } 74 75 /** 76 * Set the Auto connect priority for a paired Bluetooth Device. 77 * For example, if a device is tagged as a Primary device for a supported Bluetooth Profile, 78 * every new Auto Connect attempt would start with trying to connect to *that* device. 79 * This priority is set at a Bluetooth profile granularity 80 * 81 * @param deviceToSet - Device to set priority (Tag) 82 * @param profileToSet - BluetoothProfile to set priority for. 83 * @param priorityToSet - What priority level to set to 84 * @hide 85 */ 86 public void setBluetoothDeviceConnectionPriority(BluetoothDevice deviceToSet, int profileToSet, 87 int priorityToSet) { 88 setBluetoothDeviceConnectionPriority(deviceToSet.getAddress(), profileToSet, priorityToSet); 89 } 90 91 public void setBluetoothDeviceConnectionPriority(String deviceAddress, int profileToSet, 92 int priorityToSet) { 93 // Check if the caller has Bluetooth Admin Permissions 94 enforceBluetoothAdminPermission(); 95 if (priorityToSet == CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_1) { 96 if (!isPriorityDevicePresent(profileToSet, 97 CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0)) { 98 Log.e(TAG, "Secondary Device not allowed without a primary device"); 99 return; 100 } 101 } 102 // Write the priority preference to Secure settings. The Bluetooth device connection policy 103 // will look up the Settings when it initiates a connection 104 Settings.Secure.putStringForUser(mContext.getContentResolver(), 105 getKeyForProfile(profileToSet, priorityToSet), deviceAddress, 106 ActivityManager.getCurrentUser()); 107 108 } 109 110 /** 111 * Unset the Auto connect priority for the given profile 112 * 113 * @param profileToClear - Profile to unset priority 114 * @param priorityToClear - Which priority to clear (Primary or Secondary) 115 * @hide 116 */ 117 public void clearBluetoothDeviceConnectionPriority(int profileToClear, int priorityToClear) { 118 // Check if the caller has Bluetooth Admin F@Permissions 119 enforceBluetoothAdminPermission(); 120 if (priorityToClear == CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0) { 121 if (isPriorityDevicePresent(profileToClear, 122 CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_1)) { 123 Log.e(TAG, "Please remove Secondary device before removing Primary Device"); 124 return; 125 } 126 } 127 Settings.Secure.putStringForUser(mContext.getContentResolver(), 128 getKeyForProfile(profileToClear, priorityToClear), 129 CarBluetoothManager.BLUETOOTH_NO_PRIORITY_DEVICE, 130 ActivityManager.getCurrentUser()); 131 } 132 133 /** 134 * Returns if there is a device that has been tagged with the given priority for the given 135 * profile. 136 * 137 * @param profile - BluetoothProfile 138 * @param priorityToCheck - Priority to check 139 * @return true if there is a device present with the given priority, false if not 140 */ 141 public boolean isPriorityDevicePresent(int profile, int priorityToCheck) { 142 String deviceName = getDeviceNameWithPriority(profile, priorityToCheck); 143 if (deviceName != null && !deviceName.equalsIgnoreCase( 144 CarBluetoothManager.BLUETOOTH_NO_PRIORITY_DEVICE)) { 145 return true; 146 } else { 147 if (DBG) { 148 Log.d(TAG, 149 "No device present for priority: " + priorityToCheck + " profile: " 150 + profile); 151 } 152 return false; 153 } 154 } 155 156 /** 157 * Returns the Bluetooth device address as a String that has been tagged with the given priority 158 * for the given profile. 159 * 160 * @param profile - BluetoothProfile 161 * @param priorityToCheck - Priority to check 162 * @return BluetoothDevice address if present, null if absent 163 */ 164 public String getDeviceNameWithPriority(int profile, int priorityToCheck) { 165 String keyToQuery = null; 166 String deviceName = null; 167 enforceBluetoothAdminPermission(); 168 switch (profile) { 169 case BluetoothProfile.A2DP_SINK: 170 keyToQuery = (priorityToCheck 171 == CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0) 172 ? KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICE_PRIORITY_0 173 : KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICE_PRIORITY_1; 174 break; 175 case BluetoothProfile.HEADSET_CLIENT: 176 case BluetoothProfile.PBAP_CLIENT: 177 keyToQuery = (priorityToCheck 178 == CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0) 179 ? KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICE_PRIORITY_0 180 : KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICE_PRIORITY_1; 181 break; 182 case BluetoothProfile.MAP_CLIENT: 183 keyToQuery = (priorityToCheck 184 == CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0) 185 ? KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICE_PRIORITY_0 186 : KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICE_PRIORITY_1; 187 break; 188 default: 189 if (DBG) { 190 Log.d(TAG, "Unknown Bluetooth profile"); 191 } 192 } 193 if (keyToQuery != null) { 194 deviceName = Settings.Secure.getStringForUser(mContext.getContentResolver(), 195 keyToQuery, (int) ActivityManager.getCurrentUser()); 196 } 197 return deviceName; 198 } 199 200 private void enforceBluetoothAdminPermission() { 201 if (mContext != null 202 && PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( 203 android.Manifest.permission.BLUETOOTH_ADMIN)) { 204 return; 205 } 206 if (mContext == null) { 207 Log.e(TAG, "CarBluetoothPrioritySettings does not have a Context"); 208 } 209 throw new SecurityException("requires permission " + android.Manifest.permission.BLUETOOTH_ADMIN); 210 } 211 212 private String getKeyForProfile(int profile, int priority) { 213 String keyToLookup = null; 214 switch (profile) { 215 case BluetoothProfile.A2DP_SINK: 216 keyToLookup = (priority 217 == CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0) 218 ? KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICE_PRIORITY_0 219 : KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICE_PRIORITY_1; 220 break; 221 case BluetoothProfile.MAP_CLIENT: 222 keyToLookup = (priority 223 == CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0) 224 ? KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICE_PRIORITY_0 225 : KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICE_PRIORITY_1; 226 break; 227 case BluetoothProfile.PBAP_CLIENT: 228 // fall through 229 case BluetoothProfile.HEADSET_CLIENT: 230 keyToLookup = (priority 231 == CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0) 232 ? KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICE_PRIORITY_0 233 : KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICE_PRIORITY_1; 234 break; 235 default: 236 Log.e(TAG, "Unsupported Bluetooth profile to set priority to"); 237 break; 238 } 239 return keyToLookup; 240 } 241 242 @Override 243 public synchronized void dump(PrintWriter writer) { 244 mBluetoothDeviceConnectionPolicy.dump(writer); 245 } 246 247} 248