1 /* 2 * Copyright (c) 2016 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.pbapclient; 18 19 import android.accounts.Account; 20 import android.accounts.AccountManager; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothProfile; 23 import android.bluetooth.IBluetoothPbapClient; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.provider.CallLog; 29 import android.util.Log; 30 31 import com.android.bluetooth.R; 32 import com.android.bluetooth.btservice.AdapterService; 33 import com.android.bluetooth.btservice.ProfileService; 34 import com.android.bluetooth.sdp.SdpManager; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.concurrent.ConcurrentHashMap; 40 41 /** 42 * Provides Bluetooth Phone Book Access Profile Client profile. 43 * 44 * @hide 45 */ 46 public class PbapClientService extends ProfileService { 47 private static final boolean DBG = Utils.DBG; 48 private static final boolean VDBG = Utils.VDBG; 49 50 private static final String TAG = "PbapClientService"; 51 private static final String SERVICE_NAME = "Phonebook Access PCE"; 52 // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices. 53 private static final int MAXIMUM_DEVICES = 10; 54 private Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap = 55 new ConcurrentHashMap<>(); 56 private static PbapClientService sPbapClientService; 57 private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver(); 58 private int mSdpHandle = -1; 59 60 @Override initBinder()61 public IProfileServiceBinder initBinder() { 62 return new BluetoothPbapClientBinder(this); 63 } 64 65 @Override start()66 protected boolean start() { 67 if (VDBG) { 68 Log.v(TAG, "onStart"); 69 } 70 IntentFilter filter = new IntentFilter(); 71 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 72 // delay initial download until after the user is unlocked to add an account. 73 filter.addAction(Intent.ACTION_USER_UNLOCKED); 74 try { 75 registerReceiver(mPbapBroadcastReceiver, filter); 76 } catch (Exception e) { 77 Log.w(TAG, "Unable to register pbapclient receiver", e); 78 } 79 80 removeUncleanAccounts(); 81 registerSdpRecord(); 82 setPbapClientService(this); 83 return true; 84 } 85 86 @Override stop()87 protected boolean stop() { 88 setPbapClientService(null); 89 cleanUpSdpRecord(); 90 try { 91 unregisterReceiver(mPbapBroadcastReceiver); 92 } catch (Exception e) { 93 Log.w(TAG, "Unable to unregister pbapclient receiver", e); 94 } 95 for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) { 96 pbapClientStateMachine.doQuit(); 97 } 98 removeUncleanAccounts(); 99 return true; 100 } 101 cleanupDevice(BluetoothDevice device)102 void cleanupDevice(BluetoothDevice device) { 103 if (DBG) Log.d(TAG, "Cleanup device: " + device); 104 synchronized (mPbapClientStateMachineMap) { 105 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 106 if (pbapClientStateMachine != null) { 107 mPbapClientStateMachineMap.remove(device); 108 } 109 } 110 } 111 removeUncleanAccounts()112 private void removeUncleanAccounts() { 113 // Find all accounts that match the type "pbap" and delete them. 114 AccountManager accountManager = AccountManager.get(this); 115 Account[] accounts = 116 accountManager.getAccountsByType(getString(R.string.pbap_account_type)); 117 if (VDBG) Log.v(TAG, "Found " + accounts.length + " unclean accounts"); 118 for (Account acc : accounts) { 119 Log.w(TAG, "Deleting " + acc); 120 try { 121 getContentResolver().delete(CallLog.Calls.CONTENT_URI, 122 CallLog.Calls.PHONE_ACCOUNT_ID + "=?", new String[]{acc.name}); 123 } catch (IllegalArgumentException e) { 124 Log.w(TAG, "Call Logs could not be deleted, they may not exist yet."); 125 } 126 // The device ID is the name of the account. 127 accountManager.removeAccountExplicitly(acc); 128 } 129 } 130 registerSdpRecord()131 private void registerSdpRecord() { 132 SdpManager sdpManager = SdpManager.getDefaultManager(); 133 if (sdpManager == null) { 134 Log.e(TAG, "SdpManager is null"); 135 return; 136 } 137 mSdpHandle = sdpManager.createPbapPceRecord(SERVICE_NAME, 138 PbapClientConnectionHandler.PBAP_V1_2); 139 } 140 cleanUpSdpRecord()141 private void cleanUpSdpRecord() { 142 if (mSdpHandle < 0) { 143 Log.e(TAG, "cleanUpSdpRecord, SDP record never created"); 144 return; 145 } 146 int sdpHandle = mSdpHandle; 147 mSdpHandle = -1; 148 SdpManager sdpManager = SdpManager.getDefaultManager(); 149 if (sdpManager == null) { 150 Log.e(TAG, "cleanUpSdpRecord failed, sdpManager is null, sdpHandle=" + sdpHandle); 151 return; 152 } 153 Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle); 154 if (!sdpManager.removeSdpRecord(sdpHandle)) { 155 Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle); 156 } 157 } 158 159 160 private class PbapBroadcastReceiver extends BroadcastReceiver { 161 @Override onReceive(Context context, Intent intent)162 public void onReceive(Context context, Intent intent) { 163 String action = intent.getAction(); 164 if (DBG) Log.v(TAG, "onReceive" + action); 165 if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { 166 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 167 if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { 168 disconnect(device); 169 } 170 } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { 171 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { 172 stateMachine.resumeDownload(); 173 } 174 } 175 } 176 } 177 178 /** 179 * Handler for incoming service calls 180 */ 181 private static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub 182 implements IProfileServiceBinder { 183 private PbapClientService mService; 184 BluetoothPbapClientBinder(PbapClientService svc)185 BluetoothPbapClientBinder(PbapClientService svc) { 186 mService = svc; 187 } 188 189 @Override cleanup()190 public void cleanup() { 191 mService = null; 192 } 193 getService()194 private PbapClientService getService() { 195 if (!com.android.bluetooth.Utils.checkCaller()) { 196 Log.w(TAG, "PbapClient call not allowed for non-active user"); 197 return null; 198 } 199 200 if (mService != null && mService.isAvailable()) { 201 return mService; 202 } 203 return null; 204 } 205 206 @Override connect(BluetoothDevice device)207 public boolean connect(BluetoothDevice device) { 208 PbapClientService service = getService(); 209 if (DBG) { 210 Log.d(TAG, "PbapClient Binder connect "); 211 } 212 if (service == null) { 213 Log.e(TAG, "PbapClient Binder connect no service"); 214 return false; 215 } 216 return service.connect(device); 217 } 218 219 @Override disconnect(BluetoothDevice device)220 public boolean disconnect(BluetoothDevice device) { 221 PbapClientService service = getService(); 222 if (service == null) { 223 return false; 224 } 225 return service.disconnect(device); 226 } 227 228 @Override getConnectedDevices()229 public List<BluetoothDevice> getConnectedDevices() { 230 PbapClientService service = getService(); 231 if (service == null) { 232 return new ArrayList<BluetoothDevice>(0); 233 } 234 return service.getConnectedDevices(); 235 } 236 237 @Override getDevicesMatchingConnectionStates(int[] states)238 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 239 PbapClientService service = getService(); 240 if (service == null) { 241 return new ArrayList<BluetoothDevice>(0); 242 } 243 return service.getDevicesMatchingConnectionStates(states); 244 } 245 246 @Override getConnectionState(BluetoothDevice device)247 public int getConnectionState(BluetoothDevice device) { 248 PbapClientService service = getService(); 249 if (service == null) { 250 return BluetoothProfile.STATE_DISCONNECTED; 251 } 252 return service.getConnectionState(device); 253 } 254 255 @Override setPriority(BluetoothDevice device, int priority)256 public boolean setPriority(BluetoothDevice device, int priority) { 257 PbapClientService service = getService(); 258 if (service == null) { 259 return false; 260 } 261 return service.setPriority(device, priority); 262 } 263 264 @Override getPriority(BluetoothDevice device)265 public int getPriority(BluetoothDevice device) { 266 PbapClientService service = getService(); 267 if (service == null) { 268 return BluetoothProfile.PRIORITY_UNDEFINED; 269 } 270 return service.getPriority(device); 271 } 272 273 274 } 275 276 // API methods getPbapClientService()277 public static synchronized PbapClientService getPbapClientService() { 278 if (sPbapClientService == null) { 279 Log.w(TAG, "getPbapClientService(): service is null"); 280 return null; 281 } 282 if (!sPbapClientService.isAvailable()) { 283 Log.w(TAG, "getPbapClientService(): service is not available"); 284 return null; 285 } 286 return sPbapClientService; 287 } 288 setPbapClientService(PbapClientService instance)289 private static synchronized void setPbapClientService(PbapClientService instance) { 290 if (VDBG) { 291 Log.v(TAG, "setPbapClientService(): set to: " + instance); 292 } 293 sPbapClientService = instance; 294 } 295 connect(BluetoothDevice device)296 public boolean connect(BluetoothDevice device) { 297 if (device == null) { 298 throw new IllegalArgumentException("Null device"); 299 } 300 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 301 if (DBG) Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress()); 302 if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) { 303 return false; 304 } 305 synchronized (mPbapClientStateMachineMap) { 306 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 307 if (pbapClientStateMachine == null 308 && mPbapClientStateMachineMap.size() < MAXIMUM_DEVICES) { 309 pbapClientStateMachine = new PbapClientStateMachine(this, device); 310 pbapClientStateMachine.start(); 311 mPbapClientStateMachineMap.put(device, pbapClientStateMachine); 312 return true; 313 } else { 314 Log.w(TAG, "Received connect request while already connecting/connected."); 315 return false; 316 } 317 } 318 } 319 disconnect(BluetoothDevice device)320 boolean disconnect(BluetoothDevice device) { 321 if (device == null) { 322 throw new IllegalArgumentException("Null device"); 323 } 324 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 325 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 326 if (pbapClientStateMachine != null) { 327 pbapClientStateMachine.disconnect(device); 328 return true; 329 330 } else { 331 Log.w(TAG, "disconnect() called on unconnected device."); 332 return false; 333 } 334 } 335 getConnectedDevices()336 public List<BluetoothDevice> getConnectedDevices() { 337 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 338 int[] desiredStates = {BluetoothProfile.STATE_CONNECTED}; 339 return getDevicesMatchingConnectionStates(desiredStates); 340 } 341 getDevicesMatchingConnectionStates(int[] states)342 private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 343 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 344 List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0); 345 for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry : 346 mPbapClientStateMachineMap 347 .entrySet()) { 348 int currentDeviceState = stateMachineEntry.getValue().getConnectionState(); 349 for (int state : states) { 350 if (currentDeviceState == state) { 351 deviceList.add(stateMachineEntry.getKey()); 352 break; 353 } 354 } 355 } 356 return deviceList; 357 } 358 getConnectionState(BluetoothDevice device)359 int getConnectionState(BluetoothDevice device) { 360 if (device == null) { 361 throw new IllegalArgumentException("Null device"); 362 } 363 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 364 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 365 if (pbapClientStateMachine == null) { 366 return BluetoothProfile.STATE_DISCONNECTED; 367 } else { 368 return pbapClientStateMachine.getConnectionState(device); 369 } 370 } 371 setPriority(BluetoothDevice device, int priority)372 public boolean setPriority(BluetoothDevice device, int priority) { 373 if (device == null) { 374 throw new IllegalArgumentException("Null device"); 375 } 376 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 377 if (DBG) { 378 Log.d(TAG, "Saved priority " + device + " = " + priority); 379 } 380 AdapterService.getAdapterService().getDatabase() 381 .setProfilePriority(device, BluetoothProfile.PBAP_CLIENT, priority); 382 return true; 383 } 384 getPriority(BluetoothDevice device)385 public int getPriority(BluetoothDevice device) { 386 if (device == null) { 387 throw new IllegalArgumentException("Null device"); 388 } 389 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 390 return AdapterService.getAdapterService().getDatabase() 391 .getProfilePriority(device, BluetoothProfile.PBAP_CLIENT); 392 } 393 394 @Override dump(StringBuilder sb)395 public void dump(StringBuilder sb) { 396 super.dump(sb); 397 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { 398 stateMachine.dump(sb); 399 } 400 } 401 } 402