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