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 android.bluetooth;
18
19import java.util.List;
20import java.util.ArrayList;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.os.RemoteException;
26import android.os.Binder;
27import android.os.IBinder;
28import android.util.Log;
29
30/**
31 * This class provides the APIs to control the Bluetooth PBAP Client Profile.
32 *@hide
33 */
34public final class BluetoothPbapClient implements BluetoothProfile {
35
36    private static final String TAG = "BluetoothPbapClient";
37    private static final boolean DBG = false;
38    private static final boolean VDBG = false;
39
40    public static final String ACTION_CONNECTION_STATE_CHANGED =
41        "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
42
43    private IBluetoothPbapClient mService;
44    private final Context mContext;
45    private ServiceListener mServiceListener;
46    private BluetoothAdapter mAdapter;
47
48    /** There was an error trying to obtain the state */
49    public static final int STATE_ERROR        = -1;
50
51    public static final int RESULT_FAILURE = 0;
52    public static final int RESULT_SUCCESS = 1;
53    /** Connection canceled before completion. */
54    public static final int RESULT_CANCELED = 2;
55
56    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
57            new IBluetoothStateChangeCallback.Stub() {
58                public void onBluetoothStateChange(boolean up) {
59                    if (DBG) {
60                        Log.d(TAG, "onBluetoothStateChange: PBAP CLIENT up=" + up);
61                    }
62                    if (!up) {
63                        if (VDBG) {
64                            Log.d(TAG,"Unbinding service...");
65                        }
66                        synchronized (mConnection) {
67                            try {
68                                mService = null;
69                                mContext.unbindService(mConnection);
70                            } catch (Exception re) {
71                                Log.e(TAG,"",re);
72                            }
73                        }
74                    } else {
75                        synchronized (mConnection) {
76                            try {
77                                if (mService == null) {
78                                    if (VDBG) {
79                                        Log.d(TAG,"Binding service...");
80                                    }
81                                    doBind();
82                                }
83                            } catch (Exception re) {
84                                Log.e(TAG,"",re);
85                            }
86                        }
87                    }
88                }
89        };
90
91    /**
92     * Create a BluetoothPbapClient proxy object.
93     */
94    BluetoothPbapClient(Context context, ServiceListener l) {
95        if (DBG) {
96            Log.d(TAG, "Create BluetoothPbapClient proxy object");
97        }
98        mContext = context;
99        mServiceListener = l;
100        mAdapter = BluetoothAdapter.getDefaultAdapter();
101        IBluetoothManager mgr = mAdapter.getBluetoothManager();
102        if (mgr != null) {
103            try {
104                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
105            } catch (RemoteException e) {
106                Log.e(TAG,"",e);
107            }
108        }
109        doBind();
110    }
111
112    private boolean doBind() {
113        Intent intent = new Intent(IBluetoothPbapClient.class.getName());
114        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
115        intent.setComponent(comp);
116        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
117                android.os.Process.myUserHandle())) {
118            Log.e(TAG, "Could not bind to Bluetooth PBAP Client Service with " + intent);
119            return false;
120        }
121        return true;
122    }
123
124    protected void finalize() throws Throwable {
125        try {
126            close();
127        } finally {
128            super.finalize();
129        }
130    }
131
132    /**
133     * Close the connection to the backing service.
134     * Other public functions of BluetoothPbapClient will return default error
135     * results once close() has been called. Multiple invocations of close()
136     * are ok.
137     */
138    public synchronized void close() {
139        IBluetoothManager mgr = mAdapter.getBluetoothManager();
140        if (mgr != null) {
141            try {
142                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
143            } catch (Exception e) {
144                Log.e(TAG,"",e);
145            }
146        }
147
148        synchronized (mConnection) {
149            if (mService != null) {
150                try {
151                    mService = null;
152                    mContext.unbindService(mConnection);
153                } catch (Exception re) {
154                    Log.e(TAG,"",re);
155                }
156            }
157        }
158        mServiceListener = null;
159    }
160
161    /**
162     * Initiate connection.
163     * Upon successful connection to remote PBAP server the Client will
164     * attempt to automatically download the users phonebook and call log.
165     *
166     * @param device    a remote device we want connect to
167     * @return <code>true</code> if command has been issued successfully;
168     *         <code>false</code> otherwise;
169     */
170    public boolean connect(BluetoothDevice device) {
171        if (DBG) {
172            log("connect(" + device + ") for PBAP Client.");
173        }
174        if (mService != null && isEnabled() && isValidDevice(device)) {
175            try {
176                return mService.connect(device);
177            } catch (RemoteException e) {
178                Log.e(TAG, Log.getStackTraceString(new Throwable()));
179                return false;
180            }
181        }
182        if (mService == null) {
183            Log.w(TAG, "Proxy not attached to service");
184        }
185        return false;
186    }
187
188    /**
189     * Initiate disconnect.
190     *
191     * @param device Remote Bluetooth Device
192     * @return false on error,
193     *               true otherwise
194     */
195    public boolean disconnect(BluetoothDevice device) {
196        if (DBG) {
197            log("disconnect(" + device + ")" + new Exception() );
198        }
199        if (mService != null && isEnabled() && isValidDevice(device)) {
200            try {
201                mService.disconnect(device);
202                return true;
203            } catch (RemoteException e) {
204              Log.e(TAG, Log.getStackTraceString(new Throwable()));
205              return false;
206            }
207        }
208        if (mService == null) {
209            Log.w(TAG, "Proxy not attached to service");
210        }
211        return false;
212    }
213
214    /**
215     * Get the list of connected devices.
216     * Currently at most one.
217     *
218     * @return list of connected devices
219     */
220    @Override
221    public List<BluetoothDevice> getConnectedDevices() {
222        if (DBG) {
223            log("getConnectedDevices()");
224        }
225        if (mService != null && isEnabled()) {
226            try {
227                return mService.getConnectedDevices();
228            } catch (RemoteException e) {
229                Log.e(TAG, Log.getStackTraceString(new Throwable()));
230                return new ArrayList<BluetoothDevice>();
231            }
232        }
233        if (mService == null) {
234            Log.w(TAG, "Proxy not attached to service");
235        }
236        return new ArrayList<BluetoothDevice>();
237    }
238
239    /**
240     * Get the list of devices matching specified states. Currently at most one.
241     *
242     * @return list of matching devices
243     */
244    @Override
245    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
246        if (DBG) {
247            log("getDevicesMatchingStates()");
248        }
249        if (mService != null && isEnabled()) {
250            try {
251                return mService.getDevicesMatchingConnectionStates(states);
252            } catch (RemoteException e) {
253                Log.e(TAG, Log.getStackTraceString(new Throwable()));
254                return new ArrayList<BluetoothDevice>();
255            }
256        }
257        if (mService == null) {
258            Log.w(TAG, "Proxy not attached to service");
259        }
260        return new ArrayList<BluetoothDevice>();
261    }
262
263    /**
264     * Get connection state of device
265     *
266     * @return device connection state
267     */
268    @Override
269    public int getConnectionState(BluetoothDevice device) {
270        if (DBG) {
271            log("getConnectionState(" + device + ")");
272        }
273        if (mService != null && isEnabled() && isValidDevice(device)) {
274            try {
275                return mService.getConnectionState(device);
276            } catch (RemoteException e) {
277                Log.e(TAG, Log.getStackTraceString(new Throwable()));
278                return BluetoothProfile.STATE_DISCONNECTED;
279            }
280        }
281        if (mService == null) {
282            Log.w(TAG, "Proxy not attached to service");
283        }
284        return BluetoothProfile.STATE_DISCONNECTED;
285    }
286
287    private final ServiceConnection mConnection = new ServiceConnection() {
288        public void onServiceConnected(ComponentName className, IBinder service) {
289            if (DBG) {
290                log("Proxy object connected");
291            }
292            mService = IBluetoothPbapClient.Stub.asInterface(Binder.allowBlocking(service));
293            if (mServiceListener != null) {
294                mServiceListener.onServiceConnected(BluetoothProfile.PBAP_CLIENT, BluetoothPbapClient.this);
295            }
296        }
297        public void onServiceDisconnected(ComponentName className) {
298            if (DBG) {
299                log("Proxy object disconnected");
300            }
301            mService = null;
302            if (mServiceListener != null) {
303                mServiceListener.onServiceDisconnected(BluetoothProfile.PBAP_CLIENT);
304            }
305        }
306    };
307
308    private static void log(String msg) {
309        Log.d(TAG, msg);
310    }
311
312    private boolean isEnabled() {
313        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
314        if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) {
315            return true;
316        }
317        log("Bluetooth is Not enabled");
318        return false;
319    }
320
321    private boolean isValidDevice(BluetoothDevice device) {
322       if (device == null) {
323           return false;
324       }
325       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
326           return true;
327       }
328       return false;
329    }
330
331    /**
332     * Set priority of the profile
333     *
334     * <p> The device should already be paired.
335     *  Priority can be one of {@link #PRIORITY_ON} or
336     * {@link #PRIORITY_OFF},
337     *
338     * @param device Paired bluetooth device
339     * @param priority
340     * @return true if priority is set, false on error
341     */
342    public boolean setPriority(BluetoothDevice device, int priority) {
343        if (DBG) {
344            log("setPriority(" + device + ", " + priority + ")");
345        }
346        if (mService != null && isEnabled() &&
347            isValidDevice(device)) {
348            if (priority != BluetoothProfile.PRIORITY_OFF &&
349                priority != BluetoothProfile.PRIORITY_ON) {
350              return false;
351            }
352            try {
353                return mService.setPriority(device, priority);
354            } catch (RemoteException e) {
355                Log.e(TAG, Log.getStackTraceString(new Throwable()));
356                return false;
357            }
358        }
359        if (mService == null) {
360            Log.w(TAG, "Proxy not attached to service");
361        }
362        return false;
363    }
364
365    /**
366     * Get the priority of the profile.
367     *
368     * <p> The priority can be any of:
369     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
370     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
371     *
372     * @param device Bluetooth device
373     * @return priority of the device
374     */
375    public int getPriority(BluetoothDevice device) {
376        if (VDBG) {
377            log("getPriority(" + device + ")");
378        }
379        if (mService != null && isEnabled() && isValidDevice(device)) {
380            try {
381                return mService.getPriority(device);
382            } catch (RemoteException e) {
383                Log.e(TAG, Log.getStackTraceString(new Throwable()));
384                return PRIORITY_OFF;
385            }
386        }
387        if (mService == null) {
388            Log.w(TAG, "Proxy not attached to service");
389        }
390        return PRIORITY_OFF;
391    }
392}
393