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