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 java.util.ArrayList;
20import java.util.List;
21
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.ServiceConnection;
26import android.os.RemoteException;
27import android.os.IBinder;
28import android.os.ServiceManager;
29import android.util.Log;
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 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(IBluetoothMap.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        if (mService != null) {
199            try {
200                return mService.getState();
201            } catch (RemoteException e) {Log.e(TAG, e.toString());}
202        } else {
203            Log.w(TAG, "Proxy not attached to service");
204            if (DBG) log(Log.getStackTraceString(new Throwable()));
205        }
206        return BluetoothSap.STATE_ERROR;
207    }
208
209    /**
210     * Get the currently connected remote Bluetooth device (PCE).
211     * @return The remote Bluetooth device, or null if not in connected or
212     *         connecting state, or if this proxy object is not connected to
213     *         the Sap service.
214     * @hide
215     */
216    public BluetoothDevice getClient() {
217        if (VDBG) log("getClient()");
218        if (mService != null) {
219            try {
220                return mService.getClient();
221            } catch (RemoteException e) {Log.e(TAG, e.toString());}
222        } else {
223            Log.w(TAG, "Proxy not attached to service");
224            if (DBG) log(Log.getStackTraceString(new Throwable()));
225        }
226        return null;
227    }
228
229    /**
230     * Returns true if the specified Bluetooth device is connected.
231     * Returns false if not connected, or if this proxy object is not
232     * currently connected to the Sap service.
233     * @hide
234     */
235    public boolean isConnected(BluetoothDevice device) {
236        if (VDBG) log("isConnected(" + device + ")");
237        if (mService != null) {
238            try {
239                return mService.isConnected(device);
240            } catch (RemoteException e) {Log.e(TAG, e.toString());}
241        } else {
242            Log.w(TAG, "Proxy not attached to service");
243            if (DBG) log(Log.getStackTraceString(new Throwable()));
244        }
245        return false;
246    }
247
248    /**
249     * Initiate connection. Initiation of outgoing connections is not
250     * supported for SAP server.
251     * @hide
252     */
253    public boolean connect(BluetoothDevice device) {
254        if (DBG) log("connect(" + device + ")" + "not supported for SAPS");
255        return false;
256    }
257
258    /**
259     * Initiate disconnect.
260     *
261     * @param device Remote Bluetooth Device
262     * @return false on error,
263     *               true otherwise
264     * @hide
265     */
266    public boolean disconnect(BluetoothDevice device) {
267        if (DBG) log("disconnect(" + device + ")");
268        if (mService != null && isEnabled() &&
269            isValidDevice(device)) {
270            try {
271                return mService.disconnect(device);
272            } catch (RemoteException e) {
273              Log.e(TAG, Log.getStackTraceString(new Throwable()));
274              return false;
275            }
276        }
277        if (mService == null) Log.w(TAG, "Proxy not attached to service");
278        return false;
279    }
280
281    /**
282     * Get the list of connected devices. Currently at most one.
283     *
284     * @return list of connected devices
285     * @hide
286     */
287    public List<BluetoothDevice> getConnectedDevices() {
288        if (DBG) log("getConnectedDevices()");
289        if (mService != null && isEnabled()) {
290            try {
291                return mService.getConnectedDevices();
292            } catch (RemoteException e) {
293                Log.e(TAG, Log.getStackTraceString(new Throwable()));
294                return new ArrayList<BluetoothDevice>();
295            }
296        }
297        if (mService == null) Log.w(TAG, "Proxy not attached to service");
298        return new ArrayList<BluetoothDevice>();
299    }
300
301    /**
302     * Get the list of devices matching specified states. Currently at most one.
303     *
304     * @return list of matching devices
305     * @hide
306     */
307    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
308        if (DBG) log("getDevicesMatchingStates()");
309        if (mService != null && isEnabled()) {
310            try {
311                return mService.getDevicesMatchingConnectionStates(states);
312            } catch (RemoteException e) {
313                Log.e(TAG, Log.getStackTraceString(new Throwable()));
314                return new ArrayList<BluetoothDevice>();
315            }
316        }
317        if (mService == null) Log.w(TAG, "Proxy not attached to service");
318        return new ArrayList<BluetoothDevice>();
319    }
320
321    /**
322     * Get connection state of device
323     *
324     * @return device connection state
325     * @hide
326     */
327    public int getConnectionState(BluetoothDevice device) {
328        if (DBG) log("getConnectionState(" + device + ")");
329        if (mService != null && isEnabled() &&
330            isValidDevice(device)) {
331            try {
332                return mService.getConnectionState(device);
333            } catch (RemoteException e) {
334                Log.e(TAG, Log.getStackTraceString(new Throwable()));
335                return BluetoothProfile.STATE_DISCONNECTED;
336            }
337        }
338        if (mService == null) Log.w(TAG, "Proxy not attached to service");
339        return BluetoothProfile.STATE_DISCONNECTED;
340    }
341
342    /**
343     * Set priority of the profile
344     *
345     * <p> The device should already be paired.
346     *
347     * @param device Paired bluetooth device
348     * @param priority
349     * @return true if priority is set, false on error
350     * @hide
351     */
352    public boolean setPriority(BluetoothDevice device, int priority) {
353        if (DBG) log("setPriority(" + device + ", " + priority + ")");
354        if (mService != null && isEnabled() &&
355            isValidDevice(device)) {
356            if (priority != BluetoothProfile.PRIORITY_OFF &&
357                priority != BluetoothProfile.PRIORITY_ON) {
358              return false;
359            }
360            try {
361                return mService.setPriority(device, priority);
362            } catch (RemoteException e) {
363                Log.e(TAG, Log.getStackTraceString(new Throwable()));
364                return false;
365            }
366        }
367        if (mService == null) Log.w(TAG, "Proxy not attached to service");
368        return false;
369    }
370
371    /**
372     * Get the priority of the profile.
373     *
374     * @param device Bluetooth device
375     * @return priority of the device
376     * @hide
377     */
378    public int getPriority(BluetoothDevice device) {
379        if (VDBG) log("getPriority(" + device + ")");
380        if (mService != null && isEnabled() &&
381            isValidDevice(device)) {
382            try {
383                return mService.getPriority(device);
384            } catch (RemoteException e) {
385                Log.e(TAG, Log.getStackTraceString(new Throwable()));
386                return PRIORITY_OFF;
387            }
388        }
389        if (mService == null) Log.w(TAG, "Proxy not attached to service");
390        return PRIORITY_OFF;
391    }
392
393    private ServiceConnection mConnection = new ServiceConnection() {
394        public void onServiceConnected(ComponentName className, IBinder service) {
395            if (DBG) log("Proxy object connected");
396            mService = IBluetoothSap.Stub.asInterface(service);
397            if (mServiceListener != null) {
398                mServiceListener.onServiceConnected(BluetoothProfile.SAP, BluetoothSap.this);
399            }
400        }
401        public void onServiceDisconnected(ComponentName className) {
402            if (DBG) log("Proxy object disconnected");
403            mService = null;
404            if (mServiceListener != null) {
405                mServiceListener.onServiceDisconnected(BluetoothProfile.SAP);
406            }
407        }
408    };
409
410    private static void log(String msg) {
411        Log.d(TAG, msg);
412    }
413
414    private boolean isEnabled() {
415        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
416
417        if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON)
418            return true;
419        log("Bluetooth is Not enabled");
420        return false;
421    }
422
423    private boolean isValidDevice(BluetoothDevice device) {
424       if (device == null)
425           return false;
426
427       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress()))
428           return true;
429       return false;
430    }
431
432}
433