/* * Copyright (c) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.pbapclient; import android.accounts.Account; import android.accounts.AccountManager; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothPbapClient; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.provider.CallLog; import android.provider.Settings; import android.util.Log; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.R; import com.android.bluetooth.Utils; import java.lang.IllegalArgumentException; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; import java.util.List; import java.util.Map; /** * Provides Bluetooth Phone Book Access Profile Client profile. * * @hide */ public class PbapClientService extends ProfileService { private static final boolean DBG = false; private static final String TAG = "PbapClientService"; // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices. private static final int MAXIMUM_DEVICES = 10; private Map mPbapClientStateMachineMap = new ConcurrentHashMap<>(); private static PbapClientService sPbapClientService; private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver(); @Override protected String getName() { return TAG; } @Override public IProfileServiceBinder initBinder() { return new BluetoothPbapClientBinder(this); } @Override protected boolean start() { if (DBG) Log.d(TAG, "onStart"); IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); // delay initial download until after the user is unlocked to add an account. filter.addAction(Intent.ACTION_USER_UNLOCKED); try { registerReceiver(mPbapBroadcastReceiver, filter); } catch (Exception e) { Log.w(TAG,"Unable to register pbapclient receiver", e); } removeUncleanAccounts(); setPbapClientService(this); return true; } @Override protected boolean stop() { try { unregisterReceiver(mPbapBroadcastReceiver); } catch (Exception e) { Log.w(TAG,"Unable to unregister pbapclient receiver", e); } for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) { pbapClientStateMachine.doQuit(); } return true; } @Override protected boolean cleanup() { removeUncleanAccounts(); clearPbapClientService(); return true; } void cleanupDevice(BluetoothDevice device) { Log.w(TAG, "Cleanup device: " + device); synchronized (mPbapClientStateMachineMap) { PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); if (pbapClientStateMachine != null) { mPbapClientStateMachineMap.remove(device); } } } private void removeUncleanAccounts() { // Find all accounts that match the type "pbap" and delete them. AccountManager accountManager = AccountManager.get(this); Account[] accounts = accountManager.getAccountsByType(getString(R.string.pbap_account_type)); Log.w(TAG, "Found " + accounts.length + " unclean accounts"); for (Account acc : accounts) { Log.w(TAG, "Deleting " + acc); // The device ID is the name of the account. accountManager.removeAccountExplicitly(acc); } try { getContentResolver().delete(CallLog.Calls.CONTENT_URI, null, null); } catch (IllegalArgumentException e) { Log.w(TAG, "Call Logs could not be deleted, they may not exist yet."); } } private class PbapBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.v(TAG, "onReceive" + action); if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { disconnect(device); } } else if(action.equals(Intent.ACTION_USER_UNLOCKED)) { for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.resumeDownload(); } } } } /** * Handler for incoming service calls */ private static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub implements IProfileServiceBinder { private PbapClientService mService; public BluetoothPbapClientBinder(PbapClientService svc) { mService = svc; } @Override public boolean cleanup() { mService = null; return true; } private PbapClientService getService() { if (!Utils.checkCaller()) { Log.w(TAG, "PbapClient call not allowed for non-active user"); return null; } if (mService != null && mService.isAvailable()) { return mService; } return null; } @Override public boolean connect(BluetoothDevice device) { PbapClientService service = getService(); if (DBG) Log.d(TAG, "PbapClient Binder connect " ); if (service == null) { Log.e(TAG, "PbapClient Binder connect no service"); return false; } return service.connect(device); } @Override public boolean disconnect(BluetoothDevice device) { PbapClientService service = getService(); if (service == null) { return false; } return service.disconnect(device); } @Override public List getConnectedDevices() { PbapClientService service = getService(); if (service == null) { return new ArrayList(0); } return service.getConnectedDevices(); } @Override public List getDevicesMatchingConnectionStates(int[] states) { PbapClientService service = getService(); if (service == null) { return new ArrayList(0); } return service.getDevicesMatchingConnectionStates(states); } @Override public int getConnectionState(BluetoothDevice device) { PbapClientService service = getService(); if (service == null) { return BluetoothProfile.STATE_DISCONNECTED; } return service.getConnectionState(device); } @Override public boolean setPriority(BluetoothDevice device, int priority) { PbapClientService service = getService(); if (service == null) { return false; } return service.setPriority(device, priority); } @Override public int getPriority(BluetoothDevice device) { PbapClientService service = getService(); if (service == null) { return BluetoothProfile.PRIORITY_UNDEFINED; } return service.getPriority(device); } } // API methods public static synchronized PbapClientService getPbapClientService() { if (sPbapClientService != null && sPbapClientService.isAvailable()) { if (DBG) { Log.d(TAG, "getPbapClientService(): returning " + sPbapClientService); } return sPbapClientService; } if (DBG) { if (sPbapClientService == null) { Log.d(TAG, "getPbapClientService(): service is NULL"); } else if (!(sPbapClientService.isAvailable())) { Log.d(TAG, "getPbapClientService(): service is not available"); } } return null; } private static synchronized void setPbapClientService(PbapClientService instance) { if (instance != null && instance.isAvailable()) { if (DBG) { Log.d(TAG, "setPbapClientService(): previously set to: " + sPbapClientService); } sPbapClientService = instance; } else { if (DBG) { if (sPbapClientService == null) { Log.d(TAG, "setPbapClientService(): service not available"); } else if (!sPbapClientService.isAvailable()) { Log.d(TAG, "setPbapClientService(): service is cleaning up"); } } } } private static synchronized void clearPbapClientService() { sPbapClientService = null; } public boolean connect(BluetoothDevice device) { if (device == null) throw new IllegalArgumentException("Null device"); enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); Log.d(TAG,"Received request to ConnectPBAPPhonebook " + device.getAddress()); if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) { return false; } synchronized (mPbapClientStateMachineMap) { PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); if (pbapClientStateMachine == null && mPbapClientStateMachineMap.size() < MAXIMUM_DEVICES) { pbapClientStateMachine = new PbapClientStateMachine(this, device); pbapClientStateMachine.start(); mPbapClientStateMachineMap.put(device, pbapClientStateMachine); return true; } else { Log.w(TAG, "Received connect request while already connecting/connected."); return false; } } } boolean disconnect(BluetoothDevice device) { if (device == null) throw new IllegalArgumentException("Null device"); enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); if (pbapClientStateMachine != null) { pbapClientStateMachine.disconnect(device); return true; } else { Log.w(TAG, "disconnect() called on unconnected device."); return false; } } public List getConnectedDevices() { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); int[] desiredStates = {BluetoothProfile.STATE_CONNECTED}; return getDevicesMatchingConnectionStates(desiredStates); } private List getDevicesMatchingConnectionStates(int[] states) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List deviceList = new ArrayList(0); for (Map.Entry stateMachineEntry : mPbapClientStateMachineMap.entrySet()) { int currentDeviceState = stateMachineEntry.getValue().getConnectionState(); for (int state : states) { if (currentDeviceState == state) { deviceList.add(stateMachineEntry.getKey()); break; } } } return deviceList; } int getConnectionState(BluetoothDevice device) { if (device == null) throw new IllegalArgumentException("Null device"); enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); if (pbapClientStateMachine == null) { return BluetoothProfile.STATE_DISCONNECTED; } else { return pbapClientStateMachine.getConnectionState(device); } } public boolean setPriority(BluetoothDevice device, int priority) { if (device == null) throw new IllegalArgumentException("Null device"); enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); Settings.Global.putInt(getContentResolver(), Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()), priority); if (DBG) { Log.d(TAG,"Saved priority " + device + " = " + priority); } return true; } public int getPriority(BluetoothDevice device) { if (device == null) throw new IllegalArgumentException("Null device"); enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); int priority = Settings.Global.getInt(getContentResolver(), Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()), BluetoothProfile.PRIORITY_UNDEFINED); return priority; } @Override public void dump(StringBuilder sb) { super.dump(sb); for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.dump(sb); } } }