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