1 /*
2  * Copyright (C) 2008 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.Manifest;
20 import android.annotation.RequiresPermission;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.Context;
23 import android.os.Binder;
24 import android.os.IBinder;
25 import android.os.RemoteException;
26 import android.util.Log;
27 
28 import java.util.ArrayList;
29 import java.util.List;
30 
31 /**
32  * This class provides the APIs to control the Bluetooth SIM
33  * Access Profile (SAP).
34  *
35  * <p>BluetoothSap is a proxy object for controlling the Bluetooth
36  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
37  * the BluetoothSap proxy object.
38  *
39  * <p>Each method is protected with its appropriate permission.
40  *
41  * @hide
42  */
43 public final class BluetoothSap implements BluetoothProfile {
44 
45     private static final String TAG = "BluetoothSap";
46     private static final boolean DBG = true;
47     private static final boolean VDBG = false;
48 
49     /**
50      * Intent used to broadcast the change in connection state of the profile.
51      *
52      * <p>This intent will have 4 extras:
53      * <ul>
54      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
55      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
56      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
57      * </ul>
58      *
59      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
60      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
61      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
62      *
63      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
64      * receive.
65      *
66      * @hide
67      */
68     public static final String ACTION_CONNECTION_STATE_CHANGED =
69             "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED";
70 
71     /**
72      * There was an error trying to obtain the state.
73      *
74      * @hide
75      */
76     public static final int STATE_ERROR = -1;
77 
78     /**
79      * Connection state change succceeded.
80      *
81      * @hide
82      */
83     public static final int RESULT_SUCCESS = 1;
84 
85     /**
86      * Connection canceled before completion.
87      *
88      * @hide
89      */
90     public static final int RESULT_CANCELED = 2;
91 
92     private BluetoothAdapter mAdapter;
93     private final BluetoothProfileConnector<IBluetoothSap> mProfileConnector =
94             new BluetoothProfileConnector(this, BluetoothProfile.SAP,
95                     "BluetoothSap", IBluetoothSap.class.getName()) {
96                 @Override
97                 public IBluetoothSap getServiceInterface(IBinder service) {
98                     return IBluetoothSap.Stub.asInterface(Binder.allowBlocking(service));
99                 }
100     };
101 
102     /**
103      * Create a BluetoothSap proxy object.
104      */
BluetoothSap(Context context, ServiceListener listener)105     /*package*/ BluetoothSap(Context context, ServiceListener listener) {
106         if (DBG) Log.d(TAG, "Create BluetoothSap proxy object");
107         mAdapter = BluetoothAdapter.getDefaultAdapter();
108         mProfileConnector.connect(context, listener);
109     }
110 
finalize()111     protected void finalize() throws Throwable {
112         try {
113             close();
114         } finally {
115             super.finalize();
116         }
117     }
118 
119     /**
120      * Close the connection to the backing service.
121      * Other public functions of BluetoothSap will return default error
122      * results once close() has been called. Multiple invocations of close()
123      * are ok.
124      *
125      * @hide
126      */
close()127     public synchronized void close() {
128         mProfileConnector.disconnect();
129     }
130 
getService()131     private IBluetoothSap getService() {
132         return mProfileConnector.getService();
133     }
134 
135     /**
136      * Get the current state of the BluetoothSap service.
137      *
138      * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
139      * connected to the Sap service.
140      * @hide
141      */
getState()142     public int getState() {
143         if (VDBG) log("getState()");
144         final IBluetoothSap service = getService();
145         if (service != null) {
146             try {
147                 return service.getState();
148             } catch (RemoteException e) {
149                 Log.e(TAG, e.toString());
150             }
151         } else {
152             Log.w(TAG, "Proxy not attached to service");
153             if (DBG) log(Log.getStackTraceString(new Throwable()));
154         }
155         return BluetoothSap.STATE_ERROR;
156     }
157 
158     /**
159      * Get the currently connected remote Bluetooth device (PCE).
160      *
161      * @return The remote Bluetooth device, or null if not in connected or connecting state, or if
162      * this proxy object is not connected to the Sap service.
163      * @hide
164      */
getClient()165     public BluetoothDevice getClient() {
166         if (VDBG) log("getClient()");
167         final IBluetoothSap service = getService();
168         if (service != null) {
169             try {
170                 return service.getClient();
171             } catch (RemoteException e) {
172                 Log.e(TAG, e.toString());
173             }
174         } else {
175             Log.w(TAG, "Proxy not attached to service");
176             if (DBG) log(Log.getStackTraceString(new Throwable()));
177         }
178         return null;
179     }
180 
181     /**
182      * Returns true if the specified Bluetooth device is connected.
183      * Returns false if not connected, or if this proxy object is not
184      * currently connected to the Sap service.
185      *
186      * @hide
187      */
isConnected(BluetoothDevice device)188     public boolean isConnected(BluetoothDevice device) {
189         if (VDBG) log("isConnected(" + device + ")");
190         final IBluetoothSap service = getService();
191         if (service != null) {
192             try {
193                 return service.isConnected(device);
194             } catch (RemoteException e) {
195                 Log.e(TAG, e.toString());
196             }
197         } else {
198             Log.w(TAG, "Proxy not attached to service");
199             if (DBG) log(Log.getStackTraceString(new Throwable()));
200         }
201         return false;
202     }
203 
204     /**
205      * Initiate connection. Initiation of outgoing connections is not
206      * supported for SAP server.
207      *
208      * @hide
209      */
connect(BluetoothDevice device)210     public boolean connect(BluetoothDevice device) {
211         if (DBG) log("connect(" + device + ")" + "not supported for SAPS");
212         return false;
213     }
214 
215     /**
216      * Initiate disconnect.
217      *
218      * @param device Remote Bluetooth Device
219      * @return false on error, true otherwise
220      * @hide
221      */
222     @UnsupportedAppUsage
disconnect(BluetoothDevice device)223     public boolean disconnect(BluetoothDevice device) {
224         if (DBG) log("disconnect(" + device + ")");
225         final IBluetoothSap service = getService();
226         if (service != null && isEnabled() && isValidDevice(device)) {
227             try {
228                 return service.disconnect(device);
229             } catch (RemoteException e) {
230                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
231                 return false;
232             }
233         }
234         if (service == null) Log.w(TAG, "Proxy not attached to service");
235         return false;
236     }
237 
238     /**
239      * Get the list of connected devices. Currently at most one.
240      *
241      * @return list of connected devices
242      * @hide
243      */
getConnectedDevices()244     public List<BluetoothDevice> getConnectedDevices() {
245         if (DBG) log("getConnectedDevices()");
246         final IBluetoothSap service = getService();
247         if (service != null && isEnabled()) {
248             try {
249                 return service.getConnectedDevices();
250             } catch (RemoteException e) {
251                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
252                 return new ArrayList<BluetoothDevice>();
253             }
254         }
255         if (service == null) Log.w(TAG, "Proxy not attached to service");
256         return new ArrayList<BluetoothDevice>();
257     }
258 
259     /**
260      * Get the list of devices matching specified states. Currently at most one.
261      *
262      * @return list of matching devices
263      * @hide
264      */
getDevicesMatchingConnectionStates(int[] states)265     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
266         if (DBG) log("getDevicesMatchingStates()");
267         final IBluetoothSap service = getService();
268         if (service != null && isEnabled()) {
269             try {
270                 return service.getDevicesMatchingConnectionStates(states);
271             } catch (RemoteException e) {
272                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
273                 return new ArrayList<BluetoothDevice>();
274             }
275         }
276         if (service == null) Log.w(TAG, "Proxy not attached to service");
277         return new ArrayList<BluetoothDevice>();
278     }
279 
280     /**
281      * Get connection state of device
282      *
283      * @return device connection state
284      * @hide
285      */
getConnectionState(BluetoothDevice device)286     public int getConnectionState(BluetoothDevice device) {
287         if (DBG) log("getConnectionState(" + device + ")");
288         final IBluetoothSap service = getService();
289         if (service != null && isEnabled() && isValidDevice(device)) {
290             try {
291                 return service.getConnectionState(device);
292             } catch (RemoteException e) {
293                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
294                 return BluetoothProfile.STATE_DISCONNECTED;
295             }
296         }
297         if (service == null) Log.w(TAG, "Proxy not attached to service");
298         return BluetoothProfile.STATE_DISCONNECTED;
299     }
300 
301     /**
302      * Set priority of the profile
303      *
304      * <p> The device should already be paired.
305      * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
306      *
307      * @param device Paired bluetooth device
308      * @param priority
309      * @return true if priority is set, false on error
310      * @hide
311      */
312     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
setPriority(BluetoothDevice device, int priority)313     public boolean setPriority(BluetoothDevice device, int priority) {
314         if (DBG) log("setPriority(" + device + ", " + priority + ")");
315         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
316     }
317 
318     /**
319      * Set connection policy of the profile
320      *
321      * <p> The device should already be paired.
322      * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
323      * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
324      *
325      * @param device Paired bluetooth device
326      * @param connectionPolicy is the connection policy to set to for this profile
327      * @return true if connectionPolicy is set, false on error
328      * @hide
329      */
330     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
setConnectionPolicy(BluetoothDevice device, @ConnectionPolicy int connectionPolicy)331     public boolean setConnectionPolicy(BluetoothDevice device,
332             @ConnectionPolicy int connectionPolicy) {
333         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
334         final IBluetoothSap service = getService();
335         if (service != null && isEnabled() && isValidDevice(device)) {
336             if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
337                     && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
338                 return false;
339             }
340             try {
341                 return service.setConnectionPolicy(device, connectionPolicy);
342             } catch (RemoteException e) {
343                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
344                 return false;
345             }
346         }
347         if (service == null) Log.w(TAG, "Proxy not attached to service");
348         return false;
349     }
350 
351     /**
352      * Get the priority of the profile.
353      *
354      * <p> The priority can be any of:
355      * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
356      *
357      * @param device Bluetooth device
358      * @return priority of the device
359      * @hide
360      */
361     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
getPriority(BluetoothDevice device)362     public int getPriority(BluetoothDevice device) {
363         if (VDBG) log("getPriority(" + device + ")");
364         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
365     }
366 
367     /**
368      * Get the connection policy of the profile.
369      *
370      * <p> The connection policy can be any of:
371      * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
372      * {@link #CONNECTION_POLICY_UNKNOWN}
373      *
374      * @param device Bluetooth device
375      * @return connection policy of the device
376      * @hide
377      */
378     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
getConnectionPolicy(BluetoothDevice device)379     public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) {
380         if (VDBG) log("getConnectionPolicy(" + device + ")");
381         final IBluetoothSap service = getService();
382         if (service != null && isEnabled() && isValidDevice(device)) {
383             try {
384                 return service.getConnectionPolicy(device);
385             } catch (RemoteException e) {
386                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
387                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
388             }
389         }
390         if (service == null) Log.w(TAG, "Proxy not attached to service");
391         return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
392     }
393 
log(String msg)394     private static void log(String msg) {
395         Log.d(TAG, msg);
396     }
397 
isEnabled()398     private boolean isEnabled() {
399         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
400 
401         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) {
402             return true;
403         }
404         log("Bluetooth is Not enabled");
405         return false;
406     }
407 
isValidDevice(BluetoothDevice device)408     private static boolean isValidDevice(BluetoothDevice device) {
409         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
410     }
411 
412 }
413