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.annotation.NonNull;
20 import android.annotation.RequiresPermission;
21 import android.annotation.SdkConstant;
22 import android.annotation.SdkConstant.SdkConstantType;
23 import android.annotation.SuppressLint;
24 import android.annotation.SystemApi;
25 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
26 import android.content.AttributionSource;
27 import android.content.Context;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.util.CloseGuard;
31 import android.util.Log;
32 
33 import java.util.Collections;
34 import java.util.List;
35 
36 /**
37  * This class provides the APIs to control the Bluetooth PBAP Client Profile.
38  *
39  * @hide
40  */
41 @SystemApi
42 public final class BluetoothPbapClient implements BluetoothProfile, AutoCloseable {
43 
44     private static final String TAG = "BluetoothPbapClient";
45     private static final boolean DBG = false;
46     private static final boolean VDBG = false;
47 
48     private final CloseGuard mCloseGuard;
49 
50     /**
51      * Intent used to broadcast the change in connection state of the PBAP Client profile.
52      *
53      * <p>This intent will have 3 extras:
54      *
55      * <ul>
56      *   <li>{@link #EXTRA_STATE} - The current state of the profile.
57      *   <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
58      *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
59      * </ul>
60      *
61      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
62      * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
63      * #STATE_DISCONNECTING}.
64      *
65      * @hide
66      */
67     @SystemApi
68     @SuppressLint("ActionValue")
69     @RequiresBluetoothConnectPermission
70     @RequiresPermission(
71             allOf = {
72                 android.Manifest.permission.BLUETOOTH_CONNECT,
73                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
74             })
75     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
76     public static final String ACTION_CONNECTION_STATE_CHANGED =
77             "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED";
78 
79     /** There was an error trying to obtain the state */
80     /** @hide */
81     public static final int STATE_ERROR = -1;
82 
83     /** @hide */
84     public static final int RESULT_FAILURE = 0;
85 
86     /** @hide */
87     public static final int RESULT_SUCCESS = 1;
88 
89     /** Connection canceled before completion. */
90     /** @hide */
91     public static final int RESULT_CANCELED = 2;
92 
93     private final BluetoothAdapter mAdapter;
94     private final AttributionSource mAttributionSource;
95 
96     private IBluetoothPbapClient mService;
97 
98     /**
99      * Create a BluetoothPbapClient proxy object.
100      *
101      * @hide
102      */
BluetoothPbapClient(Context context, BluetoothAdapter adapter)103     BluetoothPbapClient(Context context, BluetoothAdapter adapter) {
104         if (DBG) {
105             Log.d(TAG, "Create BluetoothPbapClient proxy object");
106         }
107         mAdapter = adapter;
108         mAttributionSource = adapter.getAttributionSource();
109         mService = null;
110         mCloseGuard = new CloseGuard();
111         mCloseGuard.open("close");
112     }
113 
114     /** @hide */
115     @SuppressWarnings("Finalize") // TODO(b/314811467)
finalize()116     protected void finalize() {
117         if (mCloseGuard != null) {
118             mCloseGuard.warnIfOpen();
119         }
120         close();
121     }
122 
123     /**
124      * Close the connection to the backing service. Other public functions of BluetoothPbapClient
125      * will return default error results once close() has been called. Multiple invocations of
126      * close() are ok.
127      *
128      * @hide
129      */
130     @Override
close()131     public synchronized void close() {
132         mAdapter.closeProfileProxy(this);
133         if (mCloseGuard != null) {
134             mCloseGuard.close();
135         }
136     }
137 
138     /** @hide */
139     @Override
onServiceConnected(IBinder service)140     public void onServiceConnected(IBinder service) {
141         mService = IBluetoothPbapClient.Stub.asInterface(service);
142     }
143 
144     /** @hide */
145     @Override
onServiceDisconnected()146     public void onServiceDisconnected() {
147         mService = null;
148     }
149 
getService()150     private IBluetoothPbapClient getService() {
151         return mService;
152     }
153 
154     /** @hide */
155     @Override
getAdapter()156     public BluetoothAdapter getAdapter() {
157         return mAdapter;
158     }
159 
160     /**
161      * Initiate connection. Upon successful connection to remote PBAP server the Client will attempt
162      * to automatically download the users phonebook and call log.
163      *
164      * @param device a remote device we want connect to
165      * @return <code>true</code> if command has been issued successfully; <code>false</code>
166      *     otherwise;
167      * @hide
168      */
169     @RequiresBluetoothConnectPermission
170     @RequiresPermission(
171             allOf = {
172                 android.Manifest.permission.BLUETOOTH_CONNECT,
173                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
174             })
connect(BluetoothDevice device)175     public boolean connect(BluetoothDevice device) {
176         if (DBG) {
177             log("connect(" + device + ") for PBAP Client.");
178         }
179         final IBluetoothPbapClient service = getService();
180         if (service == null) {
181             Log.w(TAG, "Proxy not attached to service");
182             if (DBG) log(Log.getStackTraceString(new Throwable()));
183         } else if (isEnabled() && isValidDevice(device)) {
184             try {
185                 return service.connect(device, mAttributionSource);
186             } catch (RemoteException e) {
187                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
188             }
189         }
190         return false;
191     }
192 
193     /**
194      * Initiate disconnect.
195      *
196      * @param device Remote Bluetooth Device
197      * @return false on error, true otherwise
198      * @hide
199      */
200     @RequiresBluetoothConnectPermission
201     @RequiresPermission(
202             allOf = {
203                 android.Manifest.permission.BLUETOOTH_CONNECT,
204                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
205             })
disconnect(BluetoothDevice device)206     public boolean disconnect(BluetoothDevice device) {
207         if (DBG) {
208             log("disconnect(" + device + ")" + new Exception());
209         }
210         final IBluetoothPbapClient service = getService();
211         if (service == null) {
212             Log.w(TAG, "Proxy not attached to service");
213             if (DBG) log(Log.getStackTraceString(new Throwable()));
214         } else if (isEnabled() && isValidDevice(device)) {
215             try {
216                 return service.disconnect(device, mAttributionSource);
217             } catch (RemoteException e) {
218                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
219             }
220         }
221         return false;
222     }
223 
224     /**
225      * {@inheritDoc}
226      *
227      * @hide
228      */
229     @SystemApi
230     @Override
231     @RequiresBluetoothConnectPermission
232     @RequiresPermission(
233             allOf = {
234                 android.Manifest.permission.BLUETOOTH_CONNECT,
235                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
236             })
getConnectedDevices()237     public @NonNull List<BluetoothDevice> getConnectedDevices() {
238         if (DBG) {
239             log("getConnectedDevices()");
240         }
241         final IBluetoothPbapClient service = getService();
242         if (service == null) {
243             Log.w(TAG, "Proxy not attached to service");
244             if (DBG) log(Log.getStackTraceString(new Throwable()));
245         } else if (isEnabled()) {
246             try {
247                 return Attributable.setAttributionSource(
248                         service.getConnectedDevices(mAttributionSource), mAttributionSource);
249             } catch (RemoteException e) {
250                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
251                 throw e.rethrowAsRuntimeException();
252             }
253         }
254         return Collections.emptyList();
255     }
256 
257     /**
258      * {@inheritDoc}
259      *
260      * @hide
261      */
262     @SystemApi
263     @Override
264     @RequiresBluetoothConnectPermission
265     @RequiresPermission(
266             allOf = {
267                 android.Manifest.permission.BLUETOOTH_CONNECT,
268                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
269             })
270     @NonNull
getDevicesMatchingConnectionStates(@onNull int[] states)271     public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) {
272         if (DBG) {
273             log("getDevicesMatchingStates()");
274         }
275         final IBluetoothPbapClient service = getService();
276         if (service == null) {
277             Log.w(TAG, "Proxy not attached to service");
278             if (DBG) log(Log.getStackTraceString(new Throwable()));
279         } else if (isEnabled()) {
280             try {
281                 return Attributable.setAttributionSource(
282                         service.getDevicesMatchingConnectionStates(states, mAttributionSource),
283                         mAttributionSource);
284             } catch (RemoteException e) {
285                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
286                 throw e.rethrowAsRuntimeException();
287             }
288         }
289         return Collections.emptyList();
290     }
291 
292     /**
293      * {@inheritDoc}
294      *
295      * @hide
296      */
297     @SystemApi
298     @Override
299     @RequiresBluetoothConnectPermission
300     @RequiresPermission(
301             allOf = {
302                 android.Manifest.permission.BLUETOOTH_CONNECT,
303                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
304             })
getConnectionState(@onNull BluetoothDevice device)305     public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
306         if (DBG) {
307             log("getConnectionState(" + device + ")");
308         }
309         final IBluetoothPbapClient service = getService();
310         if (service == null) {
311             Log.w(TAG, "Proxy not attached to service");
312             if (DBG) log(Log.getStackTraceString(new Throwable()));
313         } else if (isEnabled() && isValidDevice(device)) {
314             try {
315                 return service.getConnectionState(device, mAttributionSource);
316             } catch (RemoteException e) {
317                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
318                 throw e.rethrowAsRuntimeException();
319             }
320         }
321         return BluetoothProfile.STATE_DISCONNECTED;
322     }
323 
log(String msg)324     private static void log(String msg) {
325         Log.d(TAG, msg);
326     }
327 
isEnabled()328     private boolean isEnabled() {
329         return mAdapter.isEnabled();
330     }
331 
isValidDevice(BluetoothDevice device)332     private static boolean isValidDevice(BluetoothDevice device) {
333         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
334     }
335 
336     /**
337      * Set priority of the profile
338      *
339      * <p>The device should already be paired. Priority can be one of {@link #PRIORITY_ON} or {@link
340      * #PRIORITY_OFF},
341      *
342      * @param device Paired bluetooth device
343      * @return true if priority is set, false on error
344      * @hide
345      */
346     @RequiresBluetoothConnectPermission
347     @RequiresPermission(
348             allOf = {
349                 android.Manifest.permission.BLUETOOTH_CONNECT,
350                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
351             })
setPriority(BluetoothDevice device, int priority)352     public boolean setPriority(BluetoothDevice device, int priority) {
353         if (DBG) log("setPriority(" + device + ", " + priority + ")");
354         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
355     }
356 
357     /**
358      * Set connection policy of the profile
359      *
360      * <p>The device should already be paired. Connection policy can be one of {@link
361      * #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, {@link
362      * #CONNECTION_POLICY_UNKNOWN}
363      *
364      * @param device Paired bluetooth device
365      * @param connectionPolicy is the connection policy to set to for this profile
366      * @return true if connectionPolicy is set, false on error
367      * @hide
368      */
369     @SystemApi
370     @RequiresBluetoothConnectPermission
371     @RequiresPermission(
372             allOf = {
373                 android.Manifest.permission.BLUETOOTH_CONNECT,
374                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
375             })
setConnectionPolicy( @onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)376     public boolean setConnectionPolicy(
377             @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) {
378         if (DBG) {
379             log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
380         }
381         final IBluetoothPbapClient service = getService();
382         if (service == null) {
383             Log.w(TAG, "Proxy not attached to service");
384             if (DBG) log(Log.getStackTraceString(new Throwable()));
385         } else if (isEnabled()
386                 && isValidDevice(device)
387                 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
388                         || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
389             try {
390                 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
391             } catch (RemoteException e) {
392                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
393                 throw e.rethrowAsRuntimeException();
394             }
395         }
396         return false;
397     }
398 
399     /**
400      * Get the priority of the profile.
401      *
402      * <p>The priority can be any of: {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link
403      * #PRIORITY_UNDEFINED}
404      *
405      * @param device Bluetooth device
406      * @return priority of the device
407      * @hide
408      */
409     @RequiresBluetoothConnectPermission
410     @RequiresPermission(
411             allOf = {
412                 android.Manifest.permission.BLUETOOTH_CONNECT,
413                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
414             })
getPriority(BluetoothDevice device)415     public int getPriority(BluetoothDevice device) {
416         if (VDBG) log("getPriority(" + device + ")");
417         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
418     }
419 
420     /**
421      * Get the connection policy of the profile.
422      *
423      * <p>The connection policy can be any of: {@link #CONNECTION_POLICY_ALLOWED}, {@link
424      * #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
425      *
426      * @param device Bluetooth device
427      * @return connection policy of the device
428      * @hide
429      */
430     @SystemApi
431     @RequiresBluetoothConnectPermission
432     @RequiresPermission(
433             allOf = {
434                 android.Manifest.permission.BLUETOOTH_CONNECT,
435                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
436             })
getConnectionPolicy(@onNull BluetoothDevice device)437     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
438         if (VDBG) {
439             log("getConnectionPolicy(" + device + ")");
440         }
441         final IBluetoothPbapClient service = getService();
442         if (service == null) {
443             Log.w(TAG, "Proxy not attached to service");
444             if (DBG) log(Log.getStackTraceString(new Throwable()));
445         } else if (isEnabled() && isValidDevice(device)) {
446             try {
447                 return service.getConnectionPolicy(device, mAttributionSource);
448             } catch (RemoteException e) {
449                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
450                 throw e.rethrowAsRuntimeException();
451             }
452         }
453         return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
454     }
455 }
456