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