blob: 7de309fb83124b72b883de0cf95741d32d582b47 [file] [log] [blame]
Matthew Xiefe3807a2013-07-18 17:31:50 -07001/*
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.RemoteException;
24import android.os.IBinder;
25import android.os.ServiceManager;
26import android.util.Log;
27
28/**
29 * This class provides the APIs to control the Bluetooth MAP
30 * Profile.
31 *@hide
32 */
33public class BluetoothMap {
34
35 private static final String TAG = "BluetoothMap";
36 private static final boolean DBG = true;
37 private static final boolean VDBG = false;
38
39 /** int extra for MAP_STATE_CHANGED_ACTION */
40 public static final String MAP_STATE =
41 "android.bluetooth.map.intent.MAP_STATE";
42 /** int extra for MAP_STATE_CHANGED_ACTION */
43 public static final String MAP_PREVIOUS_STATE =
44 "android.bluetooth.map.intent.MAP_PREVIOUS_STATE";
45
46 /** Indicates the state of a Map connection state has changed.
47 * This intent will always contain MAP_STATE, MAP_PREVIOUS_STATE and
48 * BluetoothIntent.ADDRESS extras.
49 */
50 public static final String MAP_STATE_CHANGED_ACTION =
51 "android.bluetooth.map.intent.action.MAP_STATE_CHANGED";
52
53 private IBluetoothMap mService;
54 private final Context mContext;
55 private ServiceListener mServiceListener;
56 private BluetoothAdapter mAdapter;
57
58 /** There was an error trying to obtain the state */
59 public static final int STATE_ERROR = -1;
60 /** No client currently connected */
61 public static final int STATE_DISCONNECTED = 0;
62 /** Connection attempt in progress */
63 public static final int STATE_CONNECTING = 1;
64 /** Client is currently connected */
65 public static final int STATE_CONNECTED = 2;
66
67 public static final int RESULT_FAILURE = 0;
68 public static final int RESULT_SUCCESS = 1;
69 /** Connection canceled before completion. */
70 public static final int RESULT_CANCELED = 2;
71
72 /**
73 * An interface for notifying Bluetooth PCE IPC clients when they have
74 * been connected to the BluetoothMap service.
75 */
76 public interface ServiceListener {
77 /**
78 * Called to notify the client when this proxy object has been
79 * connected to the BluetoothMap service. Clients must wait for
80 * this callback before making IPC calls on the BluetoothMap
81 * service.
82 */
83 public void onServiceConnected(BluetoothMap proxy);
84
85 /**
86 * Called to notify the client that this proxy object has been
87 * disconnected from the BluetoothMap service. Clients must not
88 * make IPC calls on the BluetoothMap service after this callback.
89 * This callback will currently only occur if the application hosting
90 * the BluetoothMap service, but may be called more often in future.
91 */
92 public void onServiceDisconnected();
93 }
94
95 final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
96 new IBluetoothStateChangeCallback.Stub() {
97 public void onBluetoothStateChange(boolean up) {
98 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
99 if (!up) {
100 if (VDBG) Log.d(TAG,"Unbinding service...");
101 synchronized (mConnection) {
102 try {
103 mService = null;
104 mContext.unbindService(mConnection);
105 } catch (Exception re) {
106 Log.e(TAG,"",re);
107 }
108 }
109 } else {
110 synchronized (mConnection) {
111 try {
112 if (mService == null) {
113 if (VDBG) Log.d(TAG,"Binding service...");
114 if (!mContext.bindService(
115 new Intent(IBluetoothMap.class.getName()),
116 mConnection, 0)) {
117 Log.e(TAG, "Could not bind to Bluetooth MAP Service");
118 }
119 }
120 } catch (Exception re) {
121 Log.e(TAG,"",re);
122 }
123 }
124 }
125 }
126 };
127
128 /**
129 * Create a BluetoothMap proxy object.
130 */
131 public BluetoothMap(Context context, ServiceListener l) {
132 mContext = context;
133 mServiceListener = l;
134 mAdapter = BluetoothAdapter.getDefaultAdapter();
135 IBluetoothManager mgr = mAdapter.getBluetoothManager();
136 if (mgr != null) {
137 try {
138 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
139 } catch (RemoteException e) {
140 Log.e(TAG,"",e);
141 }
142 }
143 if (!context.bindService(new Intent(IBluetoothMap.class.getName()), mConnection, 0)) {
144 Log.e(TAG, "Could not bind to Bluetooth Map Service");
145 }
146 }
147
148 protected void finalize() throws Throwable {
149 try {
150 close();
151 } finally {
152 super.finalize();
153 }
154 }
155
156 /**
157 * Close the connection to the backing service.
158 * Other public functions of BluetoothMap will return default error
159 * results once close() has been called. Multiple invocations of close()
160 * are ok.
161 */
162 public synchronized void close() {
163 IBluetoothManager mgr = mAdapter.getBluetoothManager();
164 if (mgr != null) {
165 try {
166 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
167 } catch (Exception e) {
168 Log.e(TAG,"",e);
169 }
170 }
171
172 synchronized (mConnection) {
173 if (mService != null) {
174 try {
175 mService = null;
176 mContext.unbindService(mConnection);
177 mConnection = null;
178 } catch (Exception re) {
179 Log.e(TAG,"",re);
180 }
181 }
182 }
183 mServiceListener = null;
184 }
185
186 /**
187 * Get the current state of the BluetoothMap service.
188 * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
189 * object is currently not connected to the Map service.
190 */
191 public int getState() {
192 if (VDBG) log("getState()");
193 if (mService != null) {
194 try {
195 return mService.getState();
196 } catch (RemoteException e) {Log.e(TAG, e.toString());}
197 } else {
198 Log.w(TAG, "Proxy not attached to service");
199 if (DBG) log(Log.getStackTraceString(new Throwable()));
200 }
201 return BluetoothMap.STATE_ERROR;
202 }
203
204 /**
205 * Get the currently connected remote Bluetooth device (PCE).
206 * @return The remote Bluetooth device, or null if not in connected or
207 * connecting state, or if this proxy object is not connected to
208 * the Map service.
209 */
210 public BluetoothDevice getClient() {
211 if (VDBG) log("getClient()");
212 if (mService != null) {
213 try {
214 return mService.getClient();
215 } catch (RemoteException e) {Log.e(TAG, e.toString());}
216 } else {
217 Log.w(TAG, "Proxy not attached to service");
218 if (DBG) log(Log.getStackTraceString(new Throwable()));
219 }
220 return null;
221 }
222
223 /**
224 * Returns true if the specified Bluetooth device is connected (does not
225 * include connecting). Returns false if not connected, or if this proxy
226 * object is not currently connected to the Map service.
227 */
228 public boolean isConnected(BluetoothDevice device) {
229 if (VDBG) log("isConnected(" + device + ")");
230 if (mService != null) {
231 try {
232 return mService.isConnected(device);
233 } catch (RemoteException e) {Log.e(TAG, e.toString());}
234 } else {
235 Log.w(TAG, "Proxy not attached to service");
236 if (DBG) log(Log.getStackTraceString(new Throwable()));
237 }
238 return false;
239 }
240
241 /**
242 * Disconnects the current Map Client. Currently this call blocks,
243 * it may soon be made asynchronous. Returns false if this proxy object is
244 * not currently connected to the Map service.
245 */
246 public boolean disconnect() {
247 if (DBG) log("disconnect()");
248 if (mService != null) {
249 try {
250 mService.disconnect();
251 return true;
252 } catch (RemoteException e) {Log.e(TAG, e.toString());}
253 } else {
254 Log.w(TAG, "Proxy not attached to service");
255 if (DBG) log(Log.getStackTraceString(new Throwable()));
256 }
257 return false;
258 }
259
260 /**
261 * Check class bits for possible Map support.
262 * This is a simple heuristic that tries to guess if a device with the
263 * given class bits might support Map. It is not accurate for all
264 * devices. It tries to err on the side of false positives.
265 * @return True if this device might support Map.
266 */
267 public static boolean doesClassMatchSink(BluetoothClass btClass) {
268 // TODO optimize the rule
269 switch (btClass.getDeviceClass()) {
270 case BluetoothClass.Device.COMPUTER_DESKTOP:
271 case BluetoothClass.Device.COMPUTER_LAPTOP:
272 case BluetoothClass.Device.COMPUTER_SERVER:
273 case BluetoothClass.Device.COMPUTER_UNCATEGORIZED:
274 return true;
275 default:
276 return false;
277 }
278 }
279
280 private ServiceConnection mConnection = new ServiceConnection() {
281 public void onServiceConnected(ComponentName className, IBinder service) {
282 if (DBG) log("Proxy object connected");
283 mService = IBluetoothMap.Stub.asInterface(service);
284 if (mServiceListener != null) {
285 mServiceListener.onServiceConnected(BluetoothMap.this);
286 }
287 }
288 public void onServiceDisconnected(ComponentName className) {
289 if (DBG) log("Proxy object disconnected");
290 mService = null;
291 if (mServiceListener != null) {
292 mServiceListener.onServiceDisconnected();
293 }
294 }
295 };
296
297 private static void log(String msg) {
298 Log.d(TAG, msg);
299 }
300}