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