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.bluetooth.IBluetoothHeadsetClient;
25import android.content.BroadcastReceiver;
26import android.content.ContentProviderOperation;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.OperationApplicationException;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.HandlerThread;
34import android.os.Message;
35import android.os.RemoteException;
36import android.provider.Settings;
37import android.util.Log;
38import android.provider.ContactsContract;
39
40import com.android.bluetooth.btservice.ProfileService;
41import com.android.bluetooth.Utils;
42import com.android.vcard.VCardEntry;
43
44
45import java.lang.ref.WeakReference;
46import java.util.Arrays;
47import java.util.ArrayList;
48import java.util.List;
49import java.util.HashMap;
50
51/**
52 * Provides Bluetooth Phone Book Access Profile Client profile.
53 *
54 * @hide
55 */
56public class PbapClientService extends ProfileService {
57    private static final boolean DBG = false;
58    private static final String TAG = "PbapClientService";
59    private PbapPCEClient mClient;
60    private HandlerThread mHandlerThread;
61    private AccountManager mAccountManager;
62    private static PbapClientService sPbapClientService;
63    private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver();
64
65    @Override
66    protected String getName() {
67        return TAG;
68    }
69
70    @Override
71    public IProfileServiceBinder initBinder() {
72        return new BluetoothPbapClientBinder(this);
73    }
74
75    @Override
76    protected boolean start() {
77        IntentFilter filter = new IntentFilter();
78        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
79        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
80        try {
81            registerReceiver(mPbapBroadcastReceiver, filter);
82        } catch (Exception e) {
83            Log.w(TAG,"Unable to register pbapclient receiver",e);
84        }
85        mClient = new PbapPCEClient(this);
86        mAccountManager = AccountManager.get(this);
87        setPbapClientService(this);
88        mClient.start();
89        return true;
90    }
91
92    @Override
93    protected boolean stop() {
94        try {
95            unregisterReceiver(mPbapBroadcastReceiver);
96        } catch (Exception e) {
97            Log.w(TAG,"Unable to unregister sap receiver",e);
98        }
99        mClient.disconnect(null);
100        return true;
101    }
102
103    @Override
104    protected boolean cleanup() {
105        clearPbapClientService();
106        return true;
107    }
108
109    private class PbapBroadcastReceiver extends BroadcastReceiver {
110        @Override
111        public void onReceive(Context context, Intent intent) {
112            Log.v(TAG, "onReceive");
113            String action = intent.getAction();
114            if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
115                  BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
116                  if(getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
117                      connect(device);
118                  }
119            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
120                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
121                disconnect(device);
122            }
123        }
124    }
125
126    /**
127     * Handler for incoming service calls
128     */
129    private static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub
130            implements IProfileServiceBinder {
131        private PbapClientService mService;
132
133        public BluetoothPbapClientBinder(PbapClientService svc) {
134            mService = svc;
135        }
136
137        @Override
138        public boolean cleanup() {
139            mService = null;
140            return true;
141        }
142
143        private PbapClientService getService() {
144            if (!Utils.checkCaller()) {
145                Log.w(TAG, "PbapClient call not allowed for non-active user");
146                return null;
147            }
148
149            if (mService != null && mService.isAvailable()) {
150                return mService;
151            }
152            return null;
153        }
154
155        @Override
156        public boolean connect(BluetoothDevice device) {
157            PbapClientService service = getService();
158            if (service == null) {
159                return false;
160            }
161            return service.connect(device);
162        }
163
164        @Override
165        public boolean disconnect(BluetoothDevice device) {
166            PbapClientService service = getService();
167            if (service == null) {
168                return false;
169            }
170            return service.disconnect(device);
171        }
172
173        @Override
174        public List<BluetoothDevice> getConnectedDevices() {
175            PbapClientService service = getService();
176            if (service == null) {
177                return new ArrayList<BluetoothDevice>(0);
178            }
179            return service.getConnectedDevices();
180        }
181        @Override
182        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
183            PbapClientService service = getService();
184            if (service == null) {
185                return new ArrayList<BluetoothDevice>(0);
186            }
187            return service.getDevicesMatchingConnectionStates(states);
188        }
189
190        @Override
191        public int getConnectionState(BluetoothDevice device) {
192            PbapClientService service = getService();
193            if (service == null) {
194                return BluetoothProfile.STATE_DISCONNECTED;
195            }
196            return service.getConnectionState(device);
197        }
198
199        public boolean setPriority(BluetoothDevice device, int priority) {
200            PbapClientService service = getService();
201            if (service == null) {
202                return false;
203            }
204            return service.setPriority(device, priority);
205        }
206
207        public int getPriority(BluetoothDevice device) {
208            PbapClientService service = getService();
209            if (service == null) {
210                return BluetoothProfile.PRIORITY_UNDEFINED;
211            }
212            return service.getPriority(device);
213        }
214
215
216    }
217
218
219    // API methods
220    public static synchronized PbapClientService getPbapClientService() {
221        if (sPbapClientService != null && sPbapClientService.isAvailable()) {
222            if (DBG) {
223                Log.d(TAG, "getPbapClientService(): returning " + sPbapClientService);
224            }
225            return sPbapClientService;
226        }
227        if (DBG) {
228            if (sPbapClientService == null) {
229                Log.d(TAG, "getPbapClientService(): service is NULL");
230            } else if (!(sPbapClientService.isAvailable())) {
231                Log.d(TAG, "getPbapClientService(): service is not available");
232            }
233        }
234        return null;
235    }
236
237    private static synchronized void setPbapClientService(PbapClientService instance) {
238        if (instance != null && instance.isAvailable()) {
239            if (DBG) {
240                Log.d(TAG, "setPbapClientService(): set to: " + sPbapClientService);
241            }
242            sPbapClientService = instance;
243        } else {
244            if (DBG) {
245                if (sPbapClientService == null) {
246                    Log.d(TAG, "setPbapClientService(): service not available");
247                } else if (!sPbapClientService.isAvailable()) {
248                    Log.d(TAG, "setPbapClientService(): service is cleaning up");
249                }
250            }
251        }
252    }
253
254    private static synchronized void clearPbapClientService() {
255        sPbapClientService = null;
256    }
257
258    public boolean connect(BluetoothDevice device) {
259        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
260                "Need BLUETOOTH ADMIN permission");
261        Log.d(TAG,"Received request to ConnectPBAPPhonebook " + device.getAddress());
262        int connectionState = mClient.getConnectionState();
263        if (connectionState == BluetoothProfile.STATE_CONNECTED ||
264                connectionState == BluetoothProfile.STATE_CONNECTING) {
265            return false;
266        }
267        if (getPriority(device)>BluetoothProfile.PRIORITY_OFF) {
268            mClient.connect(device);
269            return true;
270        }
271        return false;
272    }
273
274    boolean disconnect(BluetoothDevice device) {
275        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
276                "Need BLUETOOTH ADMIN permission");
277        mClient.disconnect(device);
278        return true;
279    }
280    public List<BluetoothDevice> getConnectedDevices() {
281        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
282        int[] desiredStates = {BluetoothProfile.STATE_CONNECTED};
283        return getDevicesMatchingConnectionStates(desiredStates);
284    }
285
286    private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
287        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
288        int clientState = mClient.getConnectionState();
289        Log.d(TAG,"getDevicesMatchingConnectionStates " + Arrays.toString(states) + " == " + clientState);
290        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
291        for (int state : states) {
292            if (clientState == state) {
293                BluetoothDevice currentDevice = mClient.getDevice();
294                if (currentDevice != null) {
295                    deviceList.add(currentDevice);
296                }
297            }
298        }
299        return deviceList;
300    }
301
302    int getConnectionState(BluetoothDevice device) {
303        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
304        if (device == mClient.getDevice()) {
305            return mClient.getConnectionState();
306        }
307        return BluetoothProfile.STATE_DISCONNECTED;
308    }
309
310    public boolean setPriority(BluetoothDevice device, int priority) {
311        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
312                "Need BLUETOOTH_ADMIN permission");
313        Settings.Global.putInt(getContentResolver(),
314                Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
315                priority);
316        if (DBG) {
317            Log.d(TAG,"Saved priority " + device + " = " + priority);
318        }
319        return true;
320    }
321
322    public int getPriority(BluetoothDevice device) {
323        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
324                "Need BLUETOOTH_ADMIN permission");
325        int priority = Settings.Global.getInt(getContentResolver(),
326                Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
327                BluetoothProfile.PRIORITY_UNDEFINED);
328        return priority;
329    }
330
331}
332