blob: 559a59b68b4eebf4bc0006e226e4d6aa1888dfb1 [file] [log] [blame]
Joseph Pirozzo631768d2016-09-01 14:19:28 -07001/*
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
Mathew Inwood4dc66d32018-08-01 15:07:20 +010019import android.annotation.UnsupportedAppUsage;
Joseph Pirozzo631768d2016-09-01 14:19:28 -070020import android.app.PendingIntent;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.net.Uri;
26import android.os.IBinder;
27import android.os.RemoteException;
28import android.util.Log;
29
30import java.util.ArrayList;
31import java.util.List;
32
33/**
34 * This class provides the APIs to control the Bluetooth MAP MCE Profile.
35 *
36 * @hide
37 */
38public final class BluetoothMapClient implements BluetoothProfile {
39
40 private static final String TAG = "BluetoothMapClient";
41 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
42 private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
43
44 public static final String ACTION_CONNECTION_STATE_CHANGED =
45 "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED";
46 public static final String ACTION_MESSAGE_RECEIVED =
47 "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED";
48 /* Actions to be used for pending intents */
49 public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY =
50 "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY";
51 public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
52 "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
53
Srinivas Visvanathan86a8c1c2017-03-07 10:22:53 -080054 /* Extras used in ACTION_MESSAGE_RECEIVED intent.
55 * NOTE: HANDLE is only valid for a single session with the device. */
56 public static final String EXTRA_MESSAGE_HANDLE =
57 "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
Srinivas Visvanathanad903382017-03-03 09:57:18 -080058 public static final String EXTRA_SENDER_CONTACT_URI =
59 "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
60 public static final String EXTRA_SENDER_CONTACT_NAME =
61 "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
62
Jack He16eeac32017-08-17 12:11:18 -070063 private volatile IBluetoothMapClient mService;
Joseph Pirozzo631768d2016-09-01 14:19:28 -070064 private final Context mContext;
65 private ServiceListener mServiceListener;
66 private BluetoothAdapter mAdapter;
67
68 /** There was an error trying to obtain the state */
69 public static final int STATE_ERROR = -1;
70
71 public static final int RESULT_FAILURE = 0;
72 public static final int RESULT_SUCCESS = 1;
73 /** Connection canceled before completion. */
74 public static final int RESULT_CANCELED = 2;
75
Vasu Nori694752d2018-08-17 17:25:28 -070076 private static final int UPLOADING_FEATURE_BITMASK = 0x08;
77
Jack He2992cd02017-08-22 21:21:23 -070078 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
Joseph Pirozzo631768d2016-09-01 14:19:28 -070079 new IBluetoothStateChangeCallback.Stub() {
80 public void onBluetoothStateChange(boolean up) {
81 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
82 if (!up) {
83 if (VDBG) Log.d(TAG, "Unbinding service...");
84 synchronized (mConnection) {
85 try {
86 mService = null;
87 mContext.unbindService(mConnection);
88 } catch (Exception re) {
89 Log.e(TAG, "", re);
90 }
91 }
92 } else {
93 synchronized (mConnection) {
94 try {
95 if (mService == null) {
96 if (VDBG) Log.d(TAG, "Binding service...");
97 doBind();
98 }
99 } catch (Exception re) {
100 Log.e(TAG, "", re);
101 }
102 }
103 }
104 }
105 };
106
107 /**
108 * Create a BluetoothMapClient proxy object.
109 */
110 /*package*/ BluetoothMapClient(Context context, ServiceListener l) {
111 if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object");
112 mContext = context;
113 mServiceListener = l;
114 mAdapter = BluetoothAdapter.getDefaultAdapter();
115 IBluetoothManager mgr = mAdapter.getBluetoothManager();
116 if (mgr != null) {
117 try {
118 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
119 } catch (RemoteException e) {
120 Log.e(TAG, "", e);
121 }
122 }
123 doBind();
124 }
125
126 boolean doBind() {
127 Intent intent = new Intent(IBluetoothMapClient.class.getName());
128 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
129 intent.setComponent(comp);
130 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700131 mContext.getUser())) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700132 Log.e(TAG, "Could not bind to Bluetooth MAP MCE Service with " + intent);
133 return false;
134 }
135 return true;
136 }
137
138 protected void finalize() throws Throwable {
139 try {
140 close();
141 } finally {
142 super.finalize();
143 }
144 }
145
146 /**
147 * Close the connection to the backing service.
148 * Other public functions of BluetoothMap will return default error
149 * results once close() has been called. Multiple invocations of close()
150 * are ok.
151 */
152 public void close() {
153 IBluetoothManager mgr = mAdapter.getBluetoothManager();
154 if (mgr != null) {
155 try {
156 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
157 } catch (Exception e) {
158 Log.e(TAG, "", e);
159 }
160 }
161
162 synchronized (mConnection) {
163 if (mService != null) {
164 try {
165 mService = null;
166 mContext.unbindService(mConnection);
167 } catch (Exception re) {
168 Log.e(TAG, "", re);
169 }
170 }
171 }
172 mServiceListener = null;
173 }
174
175 /**
176 * Returns true if the specified Bluetooth device is connected.
177 * Returns false if not connected, or if this proxy object is not
178 * currently connected to the Map service.
179 */
180 public boolean isConnected(BluetoothDevice device) {
181 if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700182 final IBluetoothMapClient service = mService;
183 if (service != null) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700184 try {
Jack He16eeac32017-08-17 12:11:18 -0700185 return service.isConnected(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700186 } catch (RemoteException e) {
187 Log.e(TAG, e.toString());
188 }
189 } else {
190 Log.w(TAG, "Proxy not attached to service");
191 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
192 }
193 return false;
194 }
195
196 /**
197 * Initiate connection. Initiation of outgoing connections is not
198 * supported for MAP server.
199 */
200 public boolean connect(BluetoothDevice device) {
201 if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
Jack He16eeac32017-08-17 12:11:18 -0700202 final IBluetoothMapClient service = mService;
203 if (service != null) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700204 try {
Jack He16eeac32017-08-17 12:11:18 -0700205 return service.connect(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700206 } catch (RemoteException e) {
207 Log.e(TAG, e.toString());
208 }
209 } else {
210 Log.w(TAG, "Proxy not attached to service");
211 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
212 }
213 return false;
214 }
215
216 /**
217 * Initiate disconnect.
218 *
219 * @param device Remote Bluetooth Device
220 * @return false on error, true otherwise
221 */
222 public boolean disconnect(BluetoothDevice device) {
223 if (DBG) Log.d(TAG, "disconnect(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700224 final IBluetoothMapClient service = mService;
225 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700226 try {
Jack He16eeac32017-08-17 12:11:18 -0700227 return service.disconnect(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700228 } catch (RemoteException e) {
229 Log.e(TAG, Log.getStackTraceString(new Throwable()));
230 }
231 }
Jack He16eeac32017-08-17 12:11:18 -0700232 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700233 return false;
234 }
235
236 /**
237 * Get the list of connected devices. Currently at most one.
238 *
239 * @return list of connected devices
240 */
241 @Override
242 public List<BluetoothDevice> getConnectedDevices() {
243 if (DBG) Log.d(TAG, "getConnectedDevices()");
Jack He16eeac32017-08-17 12:11:18 -0700244 final IBluetoothMapClient service = mService;
245 if (service != null && isEnabled()) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700246 try {
Jack He16eeac32017-08-17 12:11:18 -0700247 return service.getConnectedDevices();
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700248 } catch (RemoteException e) {
249 Log.e(TAG, Log.getStackTraceString(new Throwable()));
250 return new ArrayList<>();
251 }
252 }
Jack He16eeac32017-08-17 12:11:18 -0700253 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700254 return new ArrayList<>();
255 }
256
257 /**
258 * Get the list of devices matching specified states. Currently at most one.
259 *
260 * @return list of matching devices
261 */
262 @Override
263 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
264 if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
Jack He16eeac32017-08-17 12:11:18 -0700265 final IBluetoothMapClient service = mService;
266 if (service != null && isEnabled()) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700267 try {
Jack He16eeac32017-08-17 12:11:18 -0700268 return service.getDevicesMatchingConnectionStates(states);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700269 } catch (RemoteException e) {
270 Log.e(TAG, Log.getStackTraceString(new Throwable()));
271 return new ArrayList<>();
272 }
273 }
Jack He16eeac32017-08-17 12:11:18 -0700274 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700275 return new ArrayList<>();
276 }
277
278 /**
279 * Get connection state of device
280 *
281 * @return device connection state
282 */
283 @Override
284 public int getConnectionState(BluetoothDevice device) {
285 if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700286 final IBluetoothMapClient service = mService;
287 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700288 try {
Jack He16eeac32017-08-17 12:11:18 -0700289 return service.getConnectionState(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700290 } catch (RemoteException e) {
291 Log.e(TAG, Log.getStackTraceString(new Throwable()));
292 return BluetoothProfile.STATE_DISCONNECTED;
293 }
294 }
Jack He16eeac32017-08-17 12:11:18 -0700295 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700296 return BluetoothProfile.STATE_DISCONNECTED;
297 }
298
299 /**
300 * Set priority of the profile
301 *
302 * <p> The device should already be paired. Priority can be one of {@link #PRIORITY_ON} or
303 * {@link #PRIORITY_OFF},
304 *
305 * @param device Paired bluetooth device
306 * @return true if priority is set, false on error
307 */
308 public boolean setPriority(BluetoothDevice device, int priority) {
309 if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
Jack He16eeac32017-08-17 12:11:18 -0700310 final IBluetoothMapClient service = mService;
311 if (service != null && isEnabled() && isValidDevice(device)) {
Jack He2992cd02017-08-22 21:21:23 -0700312 if (priority != BluetoothProfile.PRIORITY_OFF
313 && priority != BluetoothProfile.PRIORITY_ON) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700314 return false;
315 }
316 try {
Jack He16eeac32017-08-17 12:11:18 -0700317 return service.setPriority(device, priority);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700318 } catch (RemoteException e) {
319 Log.e(TAG, Log.getStackTraceString(new Throwable()));
320 return false;
321 }
322 }
Jack He16eeac32017-08-17 12:11:18 -0700323 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700324 return false;
325 }
326
327 /**
328 * Get the priority of the profile.
329 *
330 * <p> The priority can be any of:
331 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
332 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
333 *
334 * @param device Bluetooth device
335 * @return priority of the device
336 */
337 public int getPriority(BluetoothDevice device) {
338 if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700339 final IBluetoothMapClient service = mService;
340 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700341 try {
Jack He16eeac32017-08-17 12:11:18 -0700342 return service.getPriority(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700343 } catch (RemoteException e) {
344 Log.e(TAG, Log.getStackTraceString(new Throwable()));
345 return PRIORITY_OFF;
346 }
347 }
Jack He16eeac32017-08-17 12:11:18 -0700348 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700349 return PRIORITY_OFF;
350 }
351
352 /**
353 * Send a message.
354 *
355 * Send an SMS message to either the contacts primary number or the telephone number specified.
356 *
Jack Hea355e5e2017-08-22 16:06:54 -0700357 * @param device Bluetooth device
358 * @param contacts Uri[] of the contacts
359 * @param message Message to be sent
360 * @param sentIntent intent issued when message is sent
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700361 * @param deliveredIntent intent issued when message is delivered
362 * @return true if the message is enqueued, false on error
363 */
Mathew Inwood4dc66d32018-08-01 15:07:20 +0100364 @UnsupportedAppUsage
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700365 public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
366 PendingIntent sentIntent, PendingIntent deliveredIntent) {
367 if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
Jack He16eeac32017-08-17 12:11:18 -0700368 final IBluetoothMapClient service = mService;
369 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700370 try {
Jack He16eeac32017-08-17 12:11:18 -0700371 return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700372 } catch (RemoteException e) {
373 Log.e(TAG, Log.getStackTraceString(new Throwable()));
374 return false;
375 }
376 }
377 return false;
378 }
379
380 /**
Joseph Pirozzob8fc0672016-10-06 11:44:53 -0700381 * Get unread messages. Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}.
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700382 *
383 * @param device Bluetooth device
384 * @return true if the message is enqueued, false on error
385 */
386 public boolean getUnreadMessages(BluetoothDevice device) {
387 if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
Jack He16eeac32017-08-17 12:11:18 -0700388 final IBluetoothMapClient service = mService;
389 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700390 try {
Jack He16eeac32017-08-17 12:11:18 -0700391 return service.getUnreadMessages(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700392 } catch (RemoteException e) {
393 Log.e(TAG, Log.getStackTraceString(new Throwable()));
394 return false;
395 }
396 }
397 return false;
398 }
399
Vasu Nori694752d2018-08-17 17:25:28 -0700400 /**
401 * Returns the "Uploading" feature bit value from the SDP record's
402 * MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114).
403 * @param device The Bluetooth device to get this value for.
404 * @return Returns true if the Uploading bit value in SDP record's
405 * MapSupportedFeatures field is set. False is returned otherwise.
406 */
407 public boolean isUploadingSupported(BluetoothDevice device) {
408 try {
409 return (mService != null && isEnabled() && isValidDevice(device))
410 && ((mService.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0);
411 } catch (RemoteException e) {
412 Log.e(TAG, e.getMessage());
413 }
414 return false;
415 }
416
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700417 private final ServiceConnection mConnection = new ServiceConnection() {
418 public void onServiceConnected(ComponentName className, IBinder service) {
419 if (DBG) Log.d(TAG, "Proxy object connected");
420 mService = IBluetoothMapClient.Stub.asInterface(service);
421 if (mServiceListener != null) {
422 mServiceListener.onServiceConnected(BluetoothProfile.MAP_CLIENT,
Jack Hea355e5e2017-08-22 16:06:54 -0700423 BluetoothMapClient.this);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700424 }
425 }
426
427 public void onServiceDisconnected(ComponentName className) {
428 if (DBG) Log.d(TAG, "Proxy object disconnected");
429 mService = null;
430 if (mServiceListener != null) {
431 mServiceListener.onServiceDisconnected(BluetoothProfile.MAP_CLIENT);
432 }
433 }
434 };
435
436 private boolean isEnabled() {
437 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
438 if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
439 if (DBG) Log.d(TAG, "Bluetooth is Not enabled");
440 return false;
441 }
442
Jack He16eeac32017-08-17 12:11:18 -0700443 private static boolean isValidDevice(BluetoothDevice device) {
444 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700445 }
446
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700447}