• 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 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         if (mService != null) {
180             try {
181                 return mService.isConnected(device);
182             } catch (RemoteException e) {
183                 Log.e(TAG, e.toString());
184             }
185         } else {
186             Log.w(TAG, "Proxy not attached to service");
187             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
188         }
189         return false;
190     }
191 
192     /**
193      * Initiate connection. Initiation of outgoing connections is not
194      * supported for MAP server.
195      */
connect(BluetoothDevice device)196     public boolean connect(BluetoothDevice device) {
197         if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
198         if (mService != null) {
199             try {
200                 return mService.connect(device);
201             } catch (RemoteException e) {
202                 Log.e(TAG, e.toString());
203             }
204         } else {
205             Log.w(TAG, "Proxy not attached to service");
206             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
207         }
208         return false;
209     }
210 
211     /**
212      * Initiate disconnect.
213      *
214      * @param device Remote Bluetooth Device
215      * @return false on error, true otherwise
216      */
disconnect(BluetoothDevice device)217     public boolean disconnect(BluetoothDevice device) {
218         if (DBG) Log.d(TAG, "disconnect(" + device + ")");
219         if (mService != null && isEnabled() &&
220                 isValidDevice(device)) {
221             try {
222                 return mService.disconnect(device);
223             } catch (RemoteException e) {
224                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
225             }
226         }
227         if (mService == null) Log.w(TAG, "Proxy not attached to service");
228         return false;
229     }
230 
231     /**
232      * Get the list of connected devices. Currently at most one.
233      *
234      * @return list of connected devices
235      */
236     @Override
getConnectedDevices()237     public List<BluetoothDevice> getConnectedDevices() {
238         if (DBG) Log.d(TAG, "getConnectedDevices()");
239         if (mService != null && isEnabled()) {
240             try {
241                 return mService.getConnectedDevices();
242             } catch (RemoteException e) {
243                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
244                 return new ArrayList<>();
245             }
246         }
247         if (mService == null) Log.w(TAG, "Proxy not attached to service");
248         return new ArrayList<>();
249     }
250 
251     /**
252      * Get the list of devices matching specified states. Currently at most one.
253      *
254      * @return list of matching devices
255      */
256     @Override
getDevicesMatchingConnectionStates(int[] states)257     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
258         if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
259         if (mService != null && isEnabled()) {
260             try {
261                 return mService.getDevicesMatchingConnectionStates(states);
262             } catch (RemoteException e) {
263                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
264                 return new ArrayList<>();
265             }
266         }
267         if (mService == null) Log.w(TAG, "Proxy not attached to service");
268         return new ArrayList<>();
269     }
270 
271     /**
272      * Get connection state of device
273      *
274      * @return device connection state
275      */
276     @Override
getConnectionState(BluetoothDevice device)277     public int getConnectionState(BluetoothDevice device) {
278         if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
279         if (mService != null && isEnabled() &&
280                 isValidDevice(device)) {
281             try {
282                 return mService.getConnectionState(device);
283             } catch (RemoteException e) {
284                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
285                 return BluetoothProfile.STATE_DISCONNECTED;
286             }
287         }
288         if (mService == null) Log.w(TAG, "Proxy not attached to service");
289         return BluetoothProfile.STATE_DISCONNECTED;
290     }
291 
292     /**
293      * Set priority of the profile
294      *
295      * <p> The device should already be paired.  Priority can be one of {@link #PRIORITY_ON} or
296      * {@link #PRIORITY_OFF},
297      *
298      * @param device Paired bluetooth device
299      * @return true if priority is set, false on error
300      */
setPriority(BluetoothDevice device, int priority)301     public boolean setPriority(BluetoothDevice device, int priority) {
302         if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
303         if (mService != null && isEnabled() &&
304                 isValidDevice(device)) {
305             if (priority != BluetoothProfile.PRIORITY_OFF &&
306                     priority != BluetoothProfile.PRIORITY_ON) {
307                 return false;
308             }
309             try {
310                 return mService.setPriority(device, priority);
311             } catch (RemoteException e) {
312                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
313                 return false;
314             }
315         }
316         if (mService == null) Log.w(TAG, "Proxy not attached to service");
317         return false;
318     }
319 
320     /**
321      * Get the priority of the profile.
322      *
323      * <p> The priority can be any of:
324      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
325      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
326      *
327      * @param device Bluetooth device
328      * @return priority of the device
329      */
getPriority(BluetoothDevice device)330     public int getPriority(BluetoothDevice device) {
331         if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
332         if (mService != null && isEnabled() &&
333                 isValidDevice(device)) {
334             try {
335                 return mService.getPriority(device);
336             } catch (RemoteException e) {
337                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
338                 return PRIORITY_OFF;
339             }
340         }
341         if (mService == null) Log.w(TAG, "Proxy not attached to service");
342         return PRIORITY_OFF;
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      */
sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)357     public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
358             PendingIntent sentIntent, PendingIntent deliveredIntent) {
359         if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
360         if (mService != null && isEnabled() && isValidDevice(device)) {
361             try {
362                 return mService.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
363             } catch (RemoteException e) {
364                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
365                 return false;
366             }
367         }
368         return false;
369     }
370 
371     /**
372      * Get unread messages.  Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}.
373      *
374      * @param device Bluetooth device
375      * @return true if the message is enqueued, false on error
376      */
getUnreadMessages(BluetoothDevice device)377     public boolean getUnreadMessages(BluetoothDevice device) {
378         if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
379         if (mService != null && isEnabled() && isValidDevice(device)) {
380             try {
381                 return mService.getUnreadMessages(device);
382             } catch (RemoteException e) {
383                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
384                 return false;
385             }
386         }
387         return false;
388     }
389 
390     private final ServiceConnection mConnection = new ServiceConnection() {
391         public void onServiceConnected(ComponentName className, IBinder service) {
392             if (DBG) Log.d(TAG, "Proxy object connected");
393             mService = IBluetoothMapClient.Stub.asInterface(service);
394             if (mServiceListener != null) {
395                 mServiceListener.onServiceConnected(BluetoothProfile.MAP_CLIENT,
396                     BluetoothMapClient.this);
397             }
398         }
399 
400         public void onServiceDisconnected(ComponentName className) {
401             if (DBG) Log.d(TAG, "Proxy object disconnected");
402             mService = null;
403             if (mServiceListener != null) {
404                 mServiceListener.onServiceDisconnected(BluetoothProfile.MAP_CLIENT);
405             }
406         }
407     };
408 
isEnabled()409     private boolean isEnabled() {
410         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
411         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
412         if (DBG) Log.d(TAG, "Bluetooth is Not enabled");
413         return false;
414     }
415 
isValidDevice(BluetoothDevice device)416     private boolean isValidDevice(BluetoothDevice device) {
417         if (device == null) return false;
418 
419         if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
420         return false;
421     }
422 
423 
424 }
425