blob: 19240dc0bbc76833af57a59bcf7f3824a9d23775 [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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
19import android.Manifest;
20import android.annotation.NonNull;
21import android.annotation.RequiresPermission;
22import android.annotation.SystemApi;
23import android.app.PendingIntent;
24import android.compat.annotation.UnsupportedAppUsage;
25import android.content.Context;
26import android.net.Uri;
27import android.os.Binder;
28import android.os.IBinder;
29import android.os.RemoteException;
30import android.util.Log;
31
32import java.util.ArrayList;
33import java.util.List;
34
35/**
36 * This class provides the APIs to control the Bluetooth MAP MCE Profile.
37 *
38 * @hide
39 */
40public final class BluetoothMapClient implements BluetoothProfile {
41
42 private static final String TAG = "BluetoothMapClient";
43 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
44 private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
45
46 public static final String ACTION_CONNECTION_STATE_CHANGED =
47 "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED";
48 public static final String ACTION_MESSAGE_RECEIVED =
49 "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED";
50 /* Actions to be used for pending intents */
51 public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY =
52 "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY";
53 public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
54 "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
55
56 /* Extras used in ACTION_MESSAGE_RECEIVED intent.
57 * NOTE: HANDLE is only valid for a single session with the device. */
58 public static final String EXTRA_MESSAGE_HANDLE =
59 "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
60 public static final String EXTRA_MESSAGE_TIMESTAMP =
61 "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP";
62 public static final String EXTRA_MESSAGE_READ_STATUS =
63 "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS";
64 public static final String EXTRA_SENDER_CONTACT_URI =
65 "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
66 public static final String EXTRA_SENDER_CONTACT_NAME =
67 "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
68
69 /** There was an error trying to obtain the state */
70 public static final int STATE_ERROR = -1;
71
72 public static final int RESULT_FAILURE = 0;
73 public static final int RESULT_SUCCESS = 1;
74 /** Connection canceled before completion. */
75 public static final int RESULT_CANCELED = 2;
76
77 private static final int UPLOADING_FEATURE_BITMASK = 0x08;
78
79 private BluetoothAdapter mAdapter;
80 private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector =
81 new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT,
82 "BluetoothMapClient", IBluetoothMapClient.class.getName()) {
83 @Override
84 public IBluetoothMapClient getServiceInterface(IBinder service) {
85 return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service));
86 }
87 };
88
89 /**
90 * Create a BluetoothMapClient proxy object.
91 */
92 /*package*/ BluetoothMapClient(Context context, ServiceListener listener) {
93 if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object");
94 mAdapter = BluetoothAdapter.getDefaultAdapter();
95 mProfileConnector.connect(context, listener);
96 }
97
98 protected void finalize() throws Throwable {
99 try {
100 close();
101 } finally {
102 super.finalize();
103 }
104 }
105
106 /**
107 * Close the connection to the backing service.
108 * Other public functions of BluetoothMap will return default error
109 * results once close() has been called. Multiple invocations of close()
110 * are ok.
111 */
112 public void close() {
113 mProfileConnector.disconnect();
114 }
115
116 private IBluetoothMapClient getService() {
117 return mProfileConnector.getService();
118 }
119
120 /**
121 * Returns true if the specified Bluetooth device is connected.
122 * Returns false if not connected, or if this proxy object is not
123 * currently connected to the Map service.
124 */
125 public boolean isConnected(BluetoothDevice device) {
126 if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
127 final IBluetoothMapClient service = getService();
128 if (service != null) {
129 try {
130 return service.isConnected(device);
131 } catch (RemoteException e) {
132 Log.e(TAG, e.toString());
133 }
134 } else {
135 Log.w(TAG, "Proxy not attached to service");
136 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
137 }
138 return false;
139 }
140
141 /**
142 * Initiate connection. Initiation of outgoing connections is not
143 * supported for MAP server.
144 *
145 * @hide
146 */
147 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
148 public boolean connect(BluetoothDevice device) {
149 if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
150 final IBluetoothMapClient service = getService();
151 if (service != null) {
152 try {
153 return service.connect(device);
154 } catch (RemoteException e) {
155 Log.e(TAG, e.toString());
156 }
157 } else {
158 Log.w(TAG, "Proxy not attached to service");
159 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
160 }
161 return false;
162 }
163
164 /**
165 * Initiate disconnect.
166 *
167 * @param device Remote Bluetooth Device
168 * @return false on error, true otherwise
169 *
170 * @hide
171 */
172 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
173 public boolean disconnect(BluetoothDevice device) {
174 if (DBG) Log.d(TAG, "disconnect(" + device + ")");
175 final IBluetoothMapClient service = getService();
176 if (service != null && isEnabled() && isValidDevice(device)) {
177 try {
178 return service.disconnect(device);
179 } catch (RemoteException e) {
180 Log.e(TAG, Log.getStackTraceString(new Throwable()));
181 }
182 }
183 if (service == null) Log.w(TAG, "Proxy not attached to service");
184 return false;
185 }
186
187 /**
188 * Get the list of connected devices. Currently at most one.
189 *
190 * @return list of connected devices
191 */
192 @Override
193 public List<BluetoothDevice> getConnectedDevices() {
194 if (DBG) Log.d(TAG, "getConnectedDevices()");
195 final IBluetoothMapClient service = getService();
196 if (service != null && isEnabled()) {
197 try {
198 return service.getConnectedDevices();
199 } catch (RemoteException e) {
200 Log.e(TAG, Log.getStackTraceString(new Throwable()));
201 return new ArrayList<>();
202 }
203 }
204 if (service == null) Log.w(TAG, "Proxy not attached to service");
205 return new ArrayList<>();
206 }
207
208 /**
209 * Get the list of devices matching specified states. Currently at most one.
210 *
211 * @return list of matching devices
212 */
213 @Override
214 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
215 if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
216 final IBluetoothMapClient service = getService();
217 if (service != null && isEnabled()) {
218 try {
219 return service.getDevicesMatchingConnectionStates(states);
220 } catch (RemoteException e) {
221 Log.e(TAG, Log.getStackTraceString(new Throwable()));
222 return new ArrayList<>();
223 }
224 }
225 if (service == null) Log.w(TAG, "Proxy not attached to service");
226 return new ArrayList<>();
227 }
228
229 /**
230 * Get connection state of device
231 *
232 * @return device connection state
233 */
234 @Override
235 public int getConnectionState(BluetoothDevice device) {
236 if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
237 final IBluetoothMapClient service = getService();
238 if (service != null && isEnabled() && isValidDevice(device)) {
239 try {
240 return service.getConnectionState(device);
241 } catch (RemoteException e) {
242 Log.e(TAG, Log.getStackTraceString(new Throwable()));
243 return BluetoothProfile.STATE_DISCONNECTED;
244 }
245 }
246 if (service == null) Log.w(TAG, "Proxy not attached to service");
247 return BluetoothProfile.STATE_DISCONNECTED;
248 }
249
250 /**
251 * Set priority of the profile
252 *
253 * <p> The device should already be paired.
254 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
255 *
256 * @param device Paired bluetooth device
257 * @param priority
258 * @return true if priority is set, false on error
259 * @hide
260 */
261 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
262 public boolean setPriority(BluetoothDevice device, int priority) {
263 if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
264 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
265 }
266
267 /**
268 * Set connection policy of the profile
269 *
270 * <p> The device should already be paired.
271 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
272 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
273 *
274 * @param device Paired bluetooth device
275 * @param connectionPolicy is the connection policy to set to for this profile
276 * @return true if connectionPolicy is set, false on error
277 * @hide
278 */
279 @SystemApi
280 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
281 public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
282 @ConnectionPolicy int connectionPolicy) {
283 if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
284 final IBluetoothMapClient service = getService();
285 if (service != null && isEnabled() && isValidDevice(device)) {
286 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
287 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
288 return false;
289 }
290 try {
291 return service.setConnectionPolicy(device, connectionPolicy);
292 } catch (RemoteException e) {
293 Log.e(TAG, Log.getStackTraceString(new Throwable()));
294 return false;
295 }
296 }
297 if (service == null) Log.w(TAG, "Proxy not attached to service");
298 return false;
299 }
300
301 /**
302 * Get the priority of the profile.
303 *
304 * <p> The priority can be any of:
305 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
306 *
307 * @param device Bluetooth device
308 * @return priority of the device
309 * @hide
310 */
311 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
312 public int getPriority(BluetoothDevice device) {
313 if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
314 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
315 }
316
317 /**
318 * Get the connection policy of the profile.
319 *
320 * <p> The connection policy can be any of:
321 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
322 * {@link #CONNECTION_POLICY_UNKNOWN}
323 *
324 * @param device Bluetooth device
325 * @return connection policy of the device
326 * @hide
327 */
328 @SystemApi
329 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
330 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
331 if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")");
332 final IBluetoothMapClient service = getService();
333 if (service != null && isEnabled() && isValidDevice(device)) {
334 try {
335 return service.getConnectionPolicy(device);
336 } catch (RemoteException e) {
337 Log.e(TAG, Log.getStackTraceString(new Throwable()));
338 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
339 }
340 }
341 if (service == null) Log.w(TAG, "Proxy not attached to service");
342 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
343 }
344
345 /**
346 * Send a message.
347 *
348 * Send an SMS message to either the contacts primary number or the telephone number specified.
349 *
350 * @param device Bluetooth device
351 * @param contacts Uri[] of the contacts
352 * @param message Message to be sent
353 * @param sentIntent intent issued when message is sent
354 * @param deliveredIntent intent issued when message is delivered
355 * @return true if the message is enqueued, false on error
356 */
357 @UnsupportedAppUsage
358 public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
359 PendingIntent sentIntent, PendingIntent deliveredIntent) {
360 if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
361 final IBluetoothMapClient service = getService();
362 if (service != null && isEnabled() && isValidDevice(device)) {
363 try {
364 return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
365 } catch (RemoteException e) {
366 Log.e(TAG, Log.getStackTraceString(new Throwable()));
367 return false;
368 }
369 }
370 return false;
371 }
372
373 /**
374 * Get unread messages. Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}.
375 *
376 * @param device Bluetooth device
377 * @return true if the message is enqueued, false on error
378 */
379 public boolean getUnreadMessages(BluetoothDevice device) {
380 if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
381 final IBluetoothMapClient service = getService();
382 if (service != null && isEnabled() && isValidDevice(device)) {
383 try {
384 return service.getUnreadMessages(device);
385 } catch (RemoteException e) {
386 Log.e(TAG, Log.getStackTraceString(new Throwable()));
387 return false;
388 }
389 }
390 return false;
391 }
392
393 /**
394 * Returns the "Uploading" feature bit value from the SDP record's
395 * MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114).
396 * @param device The Bluetooth device to get this value for.
397 * @return Returns true if the Uploading bit value in SDP record's
398 * MapSupportedFeatures field is set. False is returned otherwise.
399 */
400 public boolean isUploadingSupported(BluetoothDevice device) {
401 final IBluetoothMapClient service = getService();
402 try {
403 return (service != null && isEnabled() && isValidDevice(device))
404 && ((service.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0);
405 } catch (RemoteException e) {
406 Log.e(TAG, e.getMessage());
407 }
408 return false;
409 }
410
411 private boolean isEnabled() {
412 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
413 if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
414 if (DBG) Log.d(TAG, "Bluetooth is Not enabled");
415 return false;
416 }
417
418 private static boolean isValidDevice(BluetoothDevice device) {
419 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
420 }
421
422}