blob: 69682c6ab5d0df0aa8ab4376351cf03f699a357c [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;
Joseph Pirozzo631768d2016-09-01 14:19:28 -070021import android.content.Context;
Joseph Pirozzo631768d2016-09-01 14:19:28 -070022import android.net.Uri;
Ugo Yud0115462019-03-26 21:38:08 +080023import android.os.Binder;
Joseph Pirozzo631768d2016-09-01 14:19:28 -070024import 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 MAP MCE Profile.
33 *
34 * @hide
35 */
36public final class BluetoothMapClient implements BluetoothProfile {
37
38 private static final String TAG = "BluetoothMapClient";
39 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
40 private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
41
42 public static final String ACTION_CONNECTION_STATE_CHANGED =
43 "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED";
44 public static final String ACTION_MESSAGE_RECEIVED =
45 "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED";
46 /* Actions to be used for pending intents */
47 public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY =
48 "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY";
49 public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
50 "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
51
Srinivas Visvanathan86a8c1c2017-03-07 10:22:53 -080052 /* Extras used in ACTION_MESSAGE_RECEIVED intent.
53 * NOTE: HANDLE is only valid for a single session with the device. */
54 public static final String EXTRA_MESSAGE_HANDLE =
55 "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
Sal Savage5d145c02019-05-14 11:09:19 -070056 public static final String EXTRA_MESSAGE_TIMESTAMP =
57 "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP";
58 public static final String EXTRA_MESSAGE_READ_STATUS =
59 "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS";
Srinivas Visvanathanad903382017-03-03 09:57:18 -080060 public static final String EXTRA_SENDER_CONTACT_URI =
61 "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
62 public static final String EXTRA_SENDER_CONTACT_NAME =
63 "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
64
Joseph Pirozzo631768d2016-09-01 14:19:28 -070065 /** There was an error trying to obtain the state */
66 public static final int STATE_ERROR = -1;
67
68 public static final int RESULT_FAILURE = 0;
69 public static final int RESULT_SUCCESS = 1;
70 /** Connection canceled before completion. */
71 public static final int RESULT_CANCELED = 2;
72
Vasu Nori694752d2018-08-17 17:25:28 -070073 private static final int UPLOADING_FEATURE_BITMASK = 0x08;
74
Ugo Yud0115462019-03-26 21:38:08 +080075 private BluetoothAdapter mAdapter;
76 private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector =
77 new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT,
78 "BluetoothMapClient", IBluetoothMapClient.class.getName()) {
79 @Override
80 public IBluetoothMapClient getServiceInterface(IBinder service) {
81 return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service));
Joseph Pirozzo631768d2016-09-01 14:19:28 -070082 }
Ugo Yud0115462019-03-26 21:38:08 +080083 };
Joseph Pirozzo631768d2016-09-01 14:19:28 -070084
85 /**
86 * Create a BluetoothMapClient proxy object.
87 */
Ugo Yud0115462019-03-26 21:38:08 +080088 /*package*/ BluetoothMapClient(Context context, ServiceListener listener) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -070089 if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object");
Joseph Pirozzo631768d2016-09-01 14:19:28 -070090 mAdapter = BluetoothAdapter.getDefaultAdapter();
Ugo Yud0115462019-03-26 21:38:08 +080091 mProfileConnector.connect(context, listener);
Joseph Pirozzo631768d2016-09-01 14:19:28 -070092 }
93
94 protected void finalize() throws Throwable {
95 try {
96 close();
97 } finally {
98 super.finalize();
99 }
100 }
101
102 /**
103 * Close the connection to the backing service.
104 * Other public functions of BluetoothMap will return default error
105 * results once close() has been called. Multiple invocations of close()
106 * are ok.
107 */
108 public void close() {
Ugo Yud0115462019-03-26 21:38:08 +0800109 mProfileConnector.disconnect();
110 }
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700111
Ugo Yud0115462019-03-26 21:38:08 +0800112 private IBluetoothMapClient getService() {
113 return mProfileConnector.getService();
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700114 }
115
116 /**
117 * Returns true if the specified Bluetooth device is connected.
118 * Returns false if not connected, or if this proxy object is not
119 * currently connected to the Map service.
120 */
121 public boolean isConnected(BluetoothDevice device) {
122 if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800123 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700124 if (service != null) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700125 try {
Jack He16eeac32017-08-17 12:11:18 -0700126 return service.isConnected(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700127 } catch (RemoteException e) {
128 Log.e(TAG, e.toString());
129 }
130 } else {
131 Log.w(TAG, "Proxy not attached to service");
132 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
133 }
134 return false;
135 }
136
137 /**
138 * Initiate connection. Initiation of outgoing connections is not
139 * supported for MAP server.
140 */
141 public boolean connect(BluetoothDevice device) {
142 if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
Ugo Yud0115462019-03-26 21:38:08 +0800143 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700144 if (service != null) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700145 try {
Jack He16eeac32017-08-17 12:11:18 -0700146 return service.connect(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700147 } catch (RemoteException e) {
148 Log.e(TAG, e.toString());
149 }
150 } else {
151 Log.w(TAG, "Proxy not attached to service");
152 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
153 }
154 return false;
155 }
156
157 /**
158 * Initiate disconnect.
159 *
160 * @param device Remote Bluetooth Device
161 * @return false on error, true otherwise
162 */
163 public boolean disconnect(BluetoothDevice device) {
164 if (DBG) Log.d(TAG, "disconnect(" + device + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800165 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700166 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700167 try {
Jack He16eeac32017-08-17 12:11:18 -0700168 return service.disconnect(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700169 } catch (RemoteException e) {
170 Log.e(TAG, Log.getStackTraceString(new Throwable()));
171 }
172 }
Jack He16eeac32017-08-17 12:11:18 -0700173 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700174 return false;
175 }
176
177 /**
178 * Get the list of connected devices. Currently at most one.
179 *
180 * @return list of connected devices
181 */
182 @Override
183 public List<BluetoothDevice> getConnectedDevices() {
184 if (DBG) Log.d(TAG, "getConnectedDevices()");
Ugo Yud0115462019-03-26 21:38:08 +0800185 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700186 if (service != null && isEnabled()) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700187 try {
Jack He16eeac32017-08-17 12:11:18 -0700188 return service.getConnectedDevices();
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700189 } catch (RemoteException e) {
190 Log.e(TAG, Log.getStackTraceString(new Throwable()));
191 return new ArrayList<>();
192 }
193 }
Jack He16eeac32017-08-17 12:11:18 -0700194 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700195 return new ArrayList<>();
196 }
197
198 /**
199 * Get the list of devices matching specified states. Currently at most one.
200 *
201 * @return list of matching devices
202 */
203 @Override
204 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
205 if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
Ugo Yud0115462019-03-26 21:38:08 +0800206 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700207 if (service != null && isEnabled()) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700208 try {
Jack He16eeac32017-08-17 12:11:18 -0700209 return service.getDevicesMatchingConnectionStates(states);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700210 } catch (RemoteException e) {
211 Log.e(TAG, Log.getStackTraceString(new Throwable()));
212 return new ArrayList<>();
213 }
214 }
Jack He16eeac32017-08-17 12:11:18 -0700215 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700216 return new ArrayList<>();
217 }
218
219 /**
220 * Get connection state of device
221 *
222 * @return device connection state
223 */
224 @Override
225 public int getConnectionState(BluetoothDevice device) {
226 if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800227 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700228 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700229 try {
Jack He16eeac32017-08-17 12:11:18 -0700230 return service.getConnectionState(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700231 } catch (RemoteException e) {
232 Log.e(TAG, Log.getStackTraceString(new Throwable()));
233 return BluetoothProfile.STATE_DISCONNECTED;
234 }
235 }
Jack He16eeac32017-08-17 12:11:18 -0700236 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700237 return BluetoothProfile.STATE_DISCONNECTED;
238 }
239
240 /**
241 * Set priority of the profile
242 *
243 * <p> The device should already be paired. Priority can be one of {@link #PRIORITY_ON} or
244 * {@link #PRIORITY_OFF},
245 *
246 * @param device Paired bluetooth device
247 * @return true if priority is set, false on error
248 */
249 public boolean setPriority(BluetoothDevice device, int priority) {
250 if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800251 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700252 if (service != null && isEnabled() && isValidDevice(device)) {
Jack He2992cd02017-08-22 21:21:23 -0700253 if (priority != BluetoothProfile.PRIORITY_OFF
254 && priority != BluetoothProfile.PRIORITY_ON) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700255 return false;
256 }
257 try {
Jack He16eeac32017-08-17 12:11:18 -0700258 return service.setPriority(device, priority);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700259 } catch (RemoteException e) {
260 Log.e(TAG, Log.getStackTraceString(new Throwable()));
261 return false;
262 }
263 }
Jack He16eeac32017-08-17 12:11:18 -0700264 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700265 return false;
266 }
267
268 /**
269 * Get the priority of the profile.
270 *
271 * <p> The priority can be any of:
272 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
273 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
274 *
275 * @param device Bluetooth device
276 * @return priority of the device
277 */
278 public int getPriority(BluetoothDevice device) {
279 if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800280 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700281 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700282 try {
Jack He16eeac32017-08-17 12:11:18 -0700283 return service.getPriority(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700284 } catch (RemoteException e) {
285 Log.e(TAG, Log.getStackTraceString(new Throwable()));
286 return PRIORITY_OFF;
287 }
288 }
Jack He16eeac32017-08-17 12:11:18 -0700289 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700290 return PRIORITY_OFF;
291 }
292
293 /**
294 * Send a message.
295 *
296 * Send an SMS message to either the contacts primary number or the telephone number specified.
297 *
Jack Hea355e5e2017-08-22 16:06:54 -0700298 * @param device Bluetooth device
299 * @param contacts Uri[] of the contacts
300 * @param message Message to be sent
301 * @param sentIntent intent issued when message is sent
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700302 * @param deliveredIntent intent issued when message is delivered
303 * @return true if the message is enqueued, false on error
304 */
Mathew Inwood4dc66d32018-08-01 15:07:20 +0100305 @UnsupportedAppUsage
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700306 public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
307 PendingIntent sentIntent, PendingIntent deliveredIntent) {
308 if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
Ugo Yud0115462019-03-26 21:38:08 +0800309 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700310 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700311 try {
Jack He16eeac32017-08-17 12:11:18 -0700312 return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700313 } catch (RemoteException e) {
314 Log.e(TAG, Log.getStackTraceString(new Throwable()));
315 return false;
316 }
317 }
318 return false;
319 }
320
321 /**
Joseph Pirozzob8fc0672016-10-06 11:44:53 -0700322 * Get unread messages. Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}.
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700323 *
324 * @param device Bluetooth device
325 * @return true if the message is enqueued, false on error
326 */
327 public boolean getUnreadMessages(BluetoothDevice device) {
328 if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800329 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700330 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700331 try {
Jack He16eeac32017-08-17 12:11:18 -0700332 return service.getUnreadMessages(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700333 } catch (RemoteException e) {
334 Log.e(TAG, Log.getStackTraceString(new Throwable()));
335 return false;
336 }
337 }
338 return false;
339 }
340
Vasu Nori694752d2018-08-17 17:25:28 -0700341 /**
342 * Returns the "Uploading" feature bit value from the SDP record's
343 * MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114).
344 * @param device The Bluetooth device to get this value for.
345 * @return Returns true if the Uploading bit value in SDP record's
346 * MapSupportedFeatures field is set. False is returned otherwise.
347 */
348 public boolean isUploadingSupported(BluetoothDevice device) {
Ugo Yud0115462019-03-26 21:38:08 +0800349 final IBluetoothMapClient service = getService();
Vasu Nori694752d2018-08-17 17:25:28 -0700350 try {
Ugo Yud0115462019-03-26 21:38:08 +0800351 return (service != null && isEnabled() && isValidDevice(device))
352 && ((service.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0);
Vasu Nori694752d2018-08-17 17:25:28 -0700353 } catch (RemoteException e) {
354 Log.e(TAG, e.getMessage());
355 }
356 return false;
357 }
358
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700359 private boolean isEnabled() {
360 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
361 if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
362 if (DBG) Log.d(TAG, "Bluetooth is Not enabled");
363 return false;
364 }
365
Jack He16eeac32017-08-17 12:11:18 -0700366 private static boolean isValidDevice(BluetoothDevice device) {
367 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700368 }
369
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700370}