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