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.provider.Settings; 30 import android.util.Log; 31 32 import com.android.bluetooth.R; 33 import com.android.bluetooth.Utils; 34 import com.android.bluetooth.btservice.ProfileService; 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 = 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 initBinder()57 public IProfileServiceBinder initBinder() { 58 return new BluetoothPbapClientBinder(this); 59 } 60 61 @Override start()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 stop()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 cleanup()94 protected void cleanup() { 95 removeUncleanAccounts(); 96 // TODO: Should move to stop() 97 setPbapClientService(null); 98 } 99 cleanupDevice(BluetoothDevice device)100 void cleanupDevice(BluetoothDevice device) { 101 Log.w(TAG, "Cleanup device: " + device); 102 synchronized (mPbapClientStateMachineMap) { 103 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 104 if (pbapClientStateMachine != null) { 105 mPbapClientStateMachineMap.remove(device); 106 } 107 } 108 } 109 removeUncleanAccounts()110 private void removeUncleanAccounts() { 111 // Find all accounts that match the type "pbap" and delete them. 112 AccountManager accountManager = AccountManager.get(this); 113 Account[] accounts = 114 accountManager.getAccountsByType(getString(R.string.pbap_account_type)); 115 Log.w(TAG, "Found " + accounts.length + " unclean accounts"); 116 for (Account acc : accounts) { 117 Log.w(TAG, "Deleting " + acc); 118 // The device ID is the name of the account. 119 accountManager.removeAccountExplicitly(acc); 120 } 121 try { 122 getContentResolver().delete(CallLog.Calls.CONTENT_URI, null, null); 123 } catch (IllegalArgumentException e) { 124 Log.w(TAG, "Call Logs could not be deleted, they may not exist yet."); 125 } 126 } 127 128 private class PbapBroadcastReceiver extends BroadcastReceiver { 129 @Override onReceive(Context context, Intent intent)130 public void onReceive(Context context, Intent intent) { 131 String action = intent.getAction(); 132 Log.v(TAG, "onReceive" + action); 133 if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { 134 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 135 if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { 136 disconnect(device); 137 } 138 } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { 139 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { 140 stateMachine.resumeDownload(); 141 } 142 } 143 } 144 } 145 146 /** 147 * Handler for incoming service calls 148 */ 149 private static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub 150 implements IProfileServiceBinder { 151 private PbapClientService mService; 152 BluetoothPbapClientBinder(PbapClientService svc)153 BluetoothPbapClientBinder(PbapClientService svc) { 154 mService = svc; 155 } 156 157 @Override cleanup()158 public void cleanup() { 159 mService = null; 160 } 161 getService()162 private PbapClientService getService() { 163 if (!Utils.checkCaller()) { 164 Log.w(TAG, "PbapClient call not allowed for non-active user"); 165 return null; 166 } 167 168 if (mService != null && mService.isAvailable()) { 169 return mService; 170 } 171 return null; 172 } 173 174 @Override connect(BluetoothDevice device)175 public boolean connect(BluetoothDevice device) { 176 PbapClientService service = getService(); 177 if (DBG) { 178 Log.d(TAG, "PbapClient Binder connect "); 179 } 180 if (service == null) { 181 Log.e(TAG, "PbapClient Binder connect no service"); 182 return false; 183 } 184 return service.connect(device); 185 } 186 187 @Override disconnect(BluetoothDevice device)188 public boolean disconnect(BluetoothDevice device) { 189 PbapClientService service = getService(); 190 if (service == null) { 191 return false; 192 } 193 return service.disconnect(device); 194 } 195 196 @Override getConnectedDevices()197 public List<BluetoothDevice> getConnectedDevices() { 198 PbapClientService service = getService(); 199 if (service == null) { 200 return new ArrayList<BluetoothDevice>(0); 201 } 202 return service.getConnectedDevices(); 203 } 204 205 @Override getDevicesMatchingConnectionStates(int[] states)206 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 207 PbapClientService service = getService(); 208 if (service == null) { 209 return new ArrayList<BluetoothDevice>(0); 210 } 211 return service.getDevicesMatchingConnectionStates(states); 212 } 213 214 @Override getConnectionState(BluetoothDevice device)215 public int getConnectionState(BluetoothDevice device) { 216 PbapClientService service = getService(); 217 if (service == null) { 218 return BluetoothProfile.STATE_DISCONNECTED; 219 } 220 return service.getConnectionState(device); 221 } 222 223 @Override setPriority(BluetoothDevice device, int priority)224 public boolean setPriority(BluetoothDevice device, int priority) { 225 PbapClientService service = getService(); 226 if (service == null) { 227 return false; 228 } 229 return service.setPriority(device, priority); 230 } 231 232 @Override getPriority(BluetoothDevice device)233 public int getPriority(BluetoothDevice device) { 234 PbapClientService service = getService(); 235 if (service == null) { 236 return BluetoothProfile.PRIORITY_UNDEFINED; 237 } 238 return service.getPriority(device); 239 } 240 241 242 } 243 244 // API methods getPbapClientService()245 public static synchronized PbapClientService getPbapClientService() { 246 if (sPbapClientService == null) { 247 Log.w(TAG, "getPbapClientService(): service is null"); 248 return null; 249 } 250 if (!sPbapClientService.isAvailable()) { 251 Log.w(TAG, "getPbapClientService(): service is not available"); 252 return null; 253 } 254 return sPbapClientService; 255 } 256 setPbapClientService(PbapClientService instance)257 private static synchronized void setPbapClientService(PbapClientService instance) { 258 if (DBG) { 259 Log.d(TAG, "setPbapClientService(): set to: " + instance); 260 } 261 sPbapClientService = instance; 262 } 263 connect(BluetoothDevice device)264 public boolean connect(BluetoothDevice device) { 265 if (device == null) { 266 throw new IllegalArgumentException("Null device"); 267 } 268 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 269 Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress()); 270 if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) { 271 return false; 272 } 273 synchronized (mPbapClientStateMachineMap) { 274 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 275 if (pbapClientStateMachine == null 276 && mPbapClientStateMachineMap.size() < MAXIMUM_DEVICES) { 277 pbapClientStateMachine = new PbapClientStateMachine(this, device); 278 pbapClientStateMachine.start(); 279 mPbapClientStateMachineMap.put(device, pbapClientStateMachine); 280 return true; 281 } else { 282 Log.w(TAG, "Received connect request while already connecting/connected."); 283 return false; 284 } 285 } 286 } 287 disconnect(BluetoothDevice device)288 boolean disconnect(BluetoothDevice device) { 289 if (device == null) { 290 throw new IllegalArgumentException("Null device"); 291 } 292 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 293 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 294 if (pbapClientStateMachine != null) { 295 pbapClientStateMachine.disconnect(device); 296 return true; 297 298 } else { 299 Log.w(TAG, "disconnect() called on unconnected device."); 300 return false; 301 } 302 } 303 getConnectedDevices()304 public List<BluetoothDevice> getConnectedDevices() { 305 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 306 int[] desiredStates = {BluetoothProfile.STATE_CONNECTED}; 307 return getDevicesMatchingConnectionStates(desiredStates); 308 } 309 getDevicesMatchingConnectionStates(int[] states)310 private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 311 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 312 List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0); 313 for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry : 314 mPbapClientStateMachineMap 315 .entrySet()) { 316 int currentDeviceState = stateMachineEntry.getValue().getConnectionState(); 317 for (int state : states) { 318 if (currentDeviceState == state) { 319 deviceList.add(stateMachineEntry.getKey()); 320 break; 321 } 322 } 323 } 324 return deviceList; 325 } 326 getConnectionState(BluetoothDevice device)327 int getConnectionState(BluetoothDevice device) { 328 if (device == null) { 329 throw new IllegalArgumentException("Null device"); 330 } 331 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 332 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 333 if (pbapClientStateMachine == null) { 334 return BluetoothProfile.STATE_DISCONNECTED; 335 } else { 336 return pbapClientStateMachine.getConnectionState(device); 337 } 338 } 339 setPriority(BluetoothDevice device, int priority)340 public boolean setPriority(BluetoothDevice device, int priority) { 341 if (device == null) { 342 throw new IllegalArgumentException("Null device"); 343 } 344 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 345 Settings.Global.putInt(getContentResolver(), 346 Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()), priority); 347 if (DBG) { 348 Log.d(TAG, "Saved priority " + device + " = " + priority); 349 } 350 return true; 351 } 352 getPriority(BluetoothDevice device)353 public int getPriority(BluetoothDevice device) { 354 if (device == null) { 355 throw new IllegalArgumentException("Null device"); 356 } 357 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 358 int priority = Settings.Global.getInt(getContentResolver(), 359 Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()), 360 BluetoothProfile.PRIORITY_UNDEFINED); 361 return priority; 362 } 363 364 @Override dump(StringBuilder sb)365 public void dump(StringBuilder sb) { 366 super.dump(sb); 367 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { 368 stateMachine.dump(sb); 369 } 370 } 371 } 372