blob: 0ec473c85adcc2e65b3f548956ef98ac15aa4d7b [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
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -080019import android.Manifest;
20import android.annotation.RequiresPermission;
21import android.annotation.SystemApi;
Mathew Inwood4dc66d32018-08-01 15:07:20 +010022import android.annotation.UnsupportedAppUsage;
Joseph Pirozzo631768d2016-09-01 14:19:28 -070023import android.app.PendingIntent;
Joseph Pirozzo631768d2016-09-01 14:19:28 -070024import android.content.Context;
Joseph Pirozzo631768d2016-09-01 14:19:28 -070025import android.net.Uri;
Ugo Yud0115462019-03-26 21:38:08 +080026import android.os.Binder;
Joseph Pirozzo631768d2016-09-01 14:19:28 -070027import android.os.IBinder;
28import android.os.RemoteException;
29import android.util.Log;
30
31import java.util.ArrayList;
32import java.util.List;
33
34/**
35 * This class provides the APIs to control the Bluetooth MAP MCE Profile.
36 *
37 * @hide
38 */
39public final class BluetoothMapClient implements BluetoothProfile {
40
41 private static final String TAG = "BluetoothMapClient";
42 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
43 private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
44
45 public static final String ACTION_CONNECTION_STATE_CHANGED =
46 "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED";
47 public static final String ACTION_MESSAGE_RECEIVED =
48 "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED";
49 /* Actions to be used for pending intents */
50 public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY =
51 "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY";
52 public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
53 "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
54
Srinivas Visvanathan86a8c1c2017-03-07 10:22:53 -080055 /* Extras used in ACTION_MESSAGE_RECEIVED intent.
56 * NOTE: HANDLE is only valid for a single session with the device. */
57 public static final String EXTRA_MESSAGE_HANDLE =
58 "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
Sal Savage5d145c02019-05-14 11:09:19 -070059 public static final String EXTRA_MESSAGE_TIMESTAMP =
60 "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP";
61 public static final String EXTRA_MESSAGE_READ_STATUS =
62 "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS";
Srinivas Visvanathanad903382017-03-03 09:57:18 -080063 public static final String EXTRA_SENDER_CONTACT_URI =
64 "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
65 public static final String EXTRA_SENDER_CONTACT_NAME =
66 "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
67
Joseph Pirozzo631768d2016-09-01 14:19:28 -070068 /** 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
Ugo Yud0115462019-03-26 21:38:08 +080078 private BluetoothAdapter mAdapter;
79 private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector =
80 new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT,
81 "BluetoothMapClient", IBluetoothMapClient.class.getName()) {
82 @Override
83 public IBluetoothMapClient getServiceInterface(IBinder service) {
84 return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service));
Joseph Pirozzo631768d2016-09-01 14:19:28 -070085 }
Ugo Yud0115462019-03-26 21:38:08 +080086 };
Joseph Pirozzo631768d2016-09-01 14:19:28 -070087
88 /**
89 * Create a BluetoothMapClient proxy object.
90 */
Ugo Yud0115462019-03-26 21:38:08 +080091 /*package*/ BluetoothMapClient(Context context, ServiceListener listener) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -070092 if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object");
Joseph Pirozzo631768d2016-09-01 14:19:28 -070093 mAdapter = BluetoothAdapter.getDefaultAdapter();
Ugo Yud0115462019-03-26 21:38:08 +080094 mProfileConnector.connect(context, listener);
Joseph Pirozzo631768d2016-09-01 14:19:28 -070095 }
96
97 protected void finalize() throws Throwable {
98 try {
99 close();
100 } finally {
101 super.finalize();
102 }
103 }
104
105 /**
106 * Close the connection to the backing service.
107 * Other public functions of BluetoothMap will return default error
108 * results once close() has been called. Multiple invocations of close()
109 * are ok.
110 */
111 public void close() {
Ugo Yud0115462019-03-26 21:38:08 +0800112 mProfileConnector.disconnect();
113 }
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700114
Ugo Yud0115462019-03-26 21:38:08 +0800115 private IBluetoothMapClient getService() {
116 return mProfileConnector.getService();
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700117 }
118
119 /**
120 * Returns true if the specified Bluetooth device is connected.
121 * Returns false if not connected, or if this proxy object is not
122 * currently connected to the Map service.
123 */
124 public boolean isConnected(BluetoothDevice device) {
125 if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800126 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700127 if (service != null) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700128 try {
Jack He16eeac32017-08-17 12:11:18 -0700129 return service.isConnected(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700130 } catch (RemoteException e) {
131 Log.e(TAG, e.toString());
132 }
133 } else {
134 Log.w(TAG, "Proxy not attached to service");
135 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
136 }
137 return false;
138 }
139
140 /**
141 * Initiate connection. Initiation of outgoing connections is not
142 * supported for MAP server.
143 */
144 public boolean connect(BluetoothDevice device) {
145 if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
Ugo Yud0115462019-03-26 21:38:08 +0800146 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700147 if (service != null) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700148 try {
Jack He16eeac32017-08-17 12:11:18 -0700149 return service.connect(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700150 } catch (RemoteException e) {
151 Log.e(TAG, e.toString());
152 }
153 } else {
154 Log.w(TAG, "Proxy not attached to service");
155 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
156 }
157 return false;
158 }
159
160 /**
161 * Initiate disconnect.
162 *
163 * @param device Remote Bluetooth Device
164 * @return false on error, true otherwise
165 */
166 public boolean disconnect(BluetoothDevice device) {
167 if (DBG) Log.d(TAG, "disconnect(" + device + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800168 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700169 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700170 try {
Jack He16eeac32017-08-17 12:11:18 -0700171 return service.disconnect(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700172 } catch (RemoteException e) {
173 Log.e(TAG, Log.getStackTraceString(new Throwable()));
174 }
175 }
Jack He16eeac32017-08-17 12:11:18 -0700176 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700177 return false;
178 }
179
180 /**
181 * Get the list of connected devices. Currently at most one.
182 *
183 * @return list of connected devices
184 */
185 @Override
186 public List<BluetoothDevice> getConnectedDevices() {
187 if (DBG) Log.d(TAG, "getConnectedDevices()");
Ugo Yud0115462019-03-26 21:38:08 +0800188 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700189 if (service != null && isEnabled()) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700190 try {
Jack He16eeac32017-08-17 12:11:18 -0700191 return service.getConnectedDevices();
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700192 } catch (RemoteException e) {
193 Log.e(TAG, Log.getStackTraceString(new Throwable()));
194 return new ArrayList<>();
195 }
196 }
Jack He16eeac32017-08-17 12:11:18 -0700197 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700198 return new ArrayList<>();
199 }
200
201 /**
202 * Get the list of devices matching specified states. Currently at most one.
203 *
204 * @return list of matching devices
205 */
206 @Override
207 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
208 if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
Ugo Yud0115462019-03-26 21:38:08 +0800209 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700210 if (service != null && isEnabled()) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700211 try {
Jack He16eeac32017-08-17 12:11:18 -0700212 return service.getDevicesMatchingConnectionStates(states);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700213 } catch (RemoteException e) {
214 Log.e(TAG, Log.getStackTraceString(new Throwable()));
215 return new ArrayList<>();
216 }
217 }
Jack He16eeac32017-08-17 12:11:18 -0700218 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700219 return new ArrayList<>();
220 }
221
222 /**
223 * Get connection state of device
224 *
225 * @return device connection state
226 */
227 @Override
228 public int getConnectionState(BluetoothDevice device) {
229 if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800230 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700231 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700232 try {
Jack He16eeac32017-08-17 12:11:18 -0700233 return service.getConnectionState(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700234 } catch (RemoteException e) {
235 Log.e(TAG, Log.getStackTraceString(new Throwable()));
236 return BluetoothProfile.STATE_DISCONNECTED;
237 }
238 }
Jack He16eeac32017-08-17 12:11:18 -0700239 if (service == null) Log.w(TAG, "Proxy not attached to service");
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700240 return BluetoothProfile.STATE_DISCONNECTED;
241 }
242
243 /**
244 * Set priority of the profile
245 *
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800246 * <p> The device should already be paired.
247 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700248 *
249 * @param device Paired bluetooth device
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800250 * @param priority
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700251 * @return true if priority is set, false on error
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800252 * @hide
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700253 */
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800254 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700255 public boolean setPriority(BluetoothDevice device, int priority) {
256 if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800257 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
258 }
259
260 /**
261 * Set connection policy of the profile
262 *
263 * <p> The device should already be paired.
264 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
265 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
266 *
267 * @param device Paired bluetooth device
268 * @param connectionPolicy is the connection policy to set to for this profile
269 * @return true if connectionPolicy is set, false on error
270 * @hide
271 */
272 @SystemApi
273 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
274 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
275 if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800276 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700277 if (service != null && isEnabled() && isValidDevice(device)) {
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800278 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
279 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700280 return false;
281 }
282 try {
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800283 return service.setConnectionPolicy(device, connectionPolicy);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700284 } catch (RemoteException e) {
285 Log.e(TAG, Log.getStackTraceString(new Throwable()));
286 return false;
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 false;
291 }
292
293 /**
294 * Get the priority of the profile.
295 *
296 * <p> The priority can be any of:
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800297 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700298 *
299 * @param device Bluetooth device
300 * @return priority of the device
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800301 * @hide
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700302 */
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800303 @RequiresPermission(Manifest.permission.BLUETOOTH)
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700304 public int getPriority(BluetoothDevice device) {
305 if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800306 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
307 }
308
309 /**
310 * Get the connection policy of the profile.
311 *
312 * <p> The connection policy can be any of:
313 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
314 * {@link #CONNECTION_POLICY_UNKNOWN}
315 *
316 * @param device Bluetooth device
317 * @return connection policy of the device
318 * @hide
319 */
320 @SystemApi
321 @RequiresPermission(Manifest.permission.BLUETOOTH)
322 public int getConnectionPolicy(BluetoothDevice device) {
323 if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800324 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700325 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700326 try {
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800327 return service.getConnectionPolicy(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700328 } catch (RemoteException e) {
329 Log.e(TAG, Log.getStackTraceString(new Throwable()));
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800330 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700331 }
332 }
Jack He16eeac32017-08-17 12:11:18 -0700333 if (service == null) Log.w(TAG, "Proxy not attached to service");
Rahul Sabnisdf1ef4a2019-11-27 18:09:33 -0800334 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700335 }
336
337 /**
338 * Send a message.
339 *
340 * Send an SMS message to either the contacts primary number or the telephone number specified.
341 *
Jack Hea355e5e2017-08-22 16:06:54 -0700342 * @param device Bluetooth device
343 * @param contacts Uri[] of the contacts
344 * @param message Message to be sent
345 * @param sentIntent intent issued when message is sent
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700346 * @param deliveredIntent intent issued when message is delivered
347 * @return true if the message is enqueued, false on error
348 */
Mathew Inwood4dc66d32018-08-01 15:07:20 +0100349 @UnsupportedAppUsage
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700350 public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
351 PendingIntent sentIntent, PendingIntent deliveredIntent) {
352 if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
Ugo Yud0115462019-03-26 21:38:08 +0800353 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700354 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700355 try {
Jack He16eeac32017-08-17 12:11:18 -0700356 return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700357 } catch (RemoteException e) {
358 Log.e(TAG, Log.getStackTraceString(new Throwable()));
359 return false;
360 }
361 }
362 return false;
363 }
364
365 /**
Joseph Pirozzob8fc0672016-10-06 11:44:53 -0700366 * Get unread messages. Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}.
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700367 *
368 * @param device Bluetooth device
369 * @return true if the message is enqueued, false on error
370 */
371 public boolean getUnreadMessages(BluetoothDevice device) {
372 if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
Ugo Yud0115462019-03-26 21:38:08 +0800373 final IBluetoothMapClient service = getService();
Jack He16eeac32017-08-17 12:11:18 -0700374 if (service != null && isEnabled() && isValidDevice(device)) {
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700375 try {
Jack He16eeac32017-08-17 12:11:18 -0700376 return service.getUnreadMessages(device);
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700377 } catch (RemoteException e) {
378 Log.e(TAG, Log.getStackTraceString(new Throwable()));
379 return false;
380 }
381 }
382 return false;
383 }
384
Vasu Nori694752d2018-08-17 17:25:28 -0700385 /**
386 * Returns the "Uploading" feature bit value from the SDP record's
387 * MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114).
388 * @param device The Bluetooth device to get this value for.
389 * @return Returns true if the Uploading bit value in SDP record's
390 * MapSupportedFeatures field is set. False is returned otherwise.
391 */
392 public boolean isUploadingSupported(BluetoothDevice device) {
Ugo Yud0115462019-03-26 21:38:08 +0800393 final IBluetoothMapClient service = getService();
Vasu Nori694752d2018-08-17 17:25:28 -0700394 try {
Ugo Yud0115462019-03-26 21:38:08 +0800395 return (service != null && isEnabled() && isValidDevice(device))
396 && ((service.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0);
Vasu Nori694752d2018-08-17 17:25:28 -0700397 } catch (RemoteException e) {
398 Log.e(TAG, e.getMessage());
399 }
400 return false;
401 }
402
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700403 private boolean isEnabled() {
404 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
405 if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
406 if (DBG) Log.d(TAG, "Bluetooth is Not enabled");
407 return false;
408 }
409
Jack He16eeac32017-08-17 12:11:18 -0700410 private static boolean isValidDevice(BluetoothDevice device) {
411 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700412 }
413
Joseph Pirozzo631768d2016-09-01 14:19:28 -0700414}