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