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.NonNull;
21 import android.annotation.RequiresPermission;
22 import android.annotation.SdkConstant;
23 import android.annotation.SuppressLint;
24 import android.annotation.SystemApi;
25 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
26 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
27 import android.compat.annotation.UnsupportedAppUsage;
28 import android.content.Attributable;
29 import android.content.AttributionSource;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.ServiceConnection;
34 import android.content.pm.PackageManager;
35 import android.os.Build;
36 import android.os.IBinder;
37 import android.os.RemoteException;
38 import android.os.UserHandle;
39 import android.util.Log;
40 
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.List;
44 
45 /**
46  * Public API for controlling the Bluetooth Pbap Service. This includes
47  * Bluetooth Phone book Access profile.
48  * BluetoothPbap is a proxy object for controlling the Bluetooth Pbap
49  * Service via IPC.
50  *
51  * Creating a BluetoothPbap object will create a binding with the
52  * BluetoothPbap service. Users of this object should call close() when they
53  * are finished with the BluetoothPbap, so that this proxy object can unbind
54  * from the service.
55  *
56  * This BluetoothPbap object is not immediately bound to the
57  * BluetoothPbap service. Use the ServiceListener interface to obtain a
58  * notification when it is bound, this is especially important if you wish to
59  * immediately call methods on BluetoothPbap after construction.
60  *
61  * To get an instance of the BluetoothPbap class, you can call
62  * {@link BluetoothAdapter#getProfileProxy(Context, ServiceListener, int)} with the final param
63  * being {@link BluetoothProfile#PBAP}. The ServiceListener should be able to get the instance of
64  * BluetoothPbap in {@link android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected}.
65  *
66  * Android only supports one connected Bluetooth Pce at a time.
67  *
68  * @hide
69  */
70 @SystemApi
71 public class BluetoothPbap implements BluetoothProfile {
72 
73     private static final String TAG = "BluetoothPbap";
74     private static final boolean DBG = false;
75 
76     /**
77      * Intent used to broadcast the change in connection state of the PBAP
78      * profile.
79      *
80      * <p>This intent will have 3 extras:
81      * <ul>
82      * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
83      * <li> {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
84      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
85      * </ul>
86      * <p>{@link BluetoothProfile#EXTRA_STATE} or {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}
87      *  can be any of {@link BluetoothProfile#STATE_DISCONNECTED},
88      *  {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED},
89      *  {@link BluetoothProfile#STATE_DISCONNECTING}.
90      *
91      * @hide
92      */
93     @SuppressLint("ActionValue")
94     @SystemApi
95     @RequiresBluetoothConnectPermission
96     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
97     @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
98     public static final String ACTION_CONNECTION_STATE_CHANGED =
99             "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
100 
101     private volatile IBluetoothPbap mService;
102     private final Context mContext;
103     private ServiceListener mServiceListener;
104     private final BluetoothAdapter mAdapter;
105     private final AttributionSource mAttributionSource;
106 
107     /** @hide */
108     public static final int RESULT_FAILURE = 0;
109     /** @hide */
110     public static final int RESULT_SUCCESS = 1;
111     /**
112      * Connection canceled before completion.
113      *
114      * @hide
115      */
116     public static final int RESULT_CANCELED = 2;
117 
118     @SuppressLint("AndroidFrameworkBluetoothPermission")
119     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
120             new IBluetoothStateChangeCallback.Stub() {
121                 public void onBluetoothStateChange(boolean up) {
122                     log("onBluetoothStateChange: up=" + up);
123                     if (!up) {
124                         doUnbind();
125                     } else {
126                         doBind();
127                     }
128                 }
129             };
130 
131     /**
132      * Create a BluetoothPbap proxy object.
133      *
134      * @hide
135      */
BluetoothPbap(Context context, ServiceListener l, BluetoothAdapter adapter)136     public BluetoothPbap(Context context, ServiceListener l, BluetoothAdapter adapter) {
137         mContext = context;
138         mServiceListener = l;
139         mAdapter = adapter;
140         mAttributionSource = adapter.getAttributionSource();
141 
142         // Preserve legacy compatibility where apps were depending on
143         // registerStateChangeCallback() performing a permissions check which
144         // has been relaxed in modern platform versions
145         if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R
146                 && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH)
147                         != PackageManager.PERMISSION_GRANTED) {
148             throw new SecurityException("Need BLUETOOTH permission");
149         }
150 
151         IBluetoothManager mgr = mAdapter.getBluetoothManager();
152         if (mgr != null) {
153             try {
154                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
155             } catch (RemoteException re) {
156                 Log.e(TAG, "", re);
157             }
158         }
159         doBind();
160     }
161 
162     @SuppressLint("AndroidFrameworkRequiresPermission")
doBind()163     boolean doBind() {
164         synchronized (mConnection) {
165             try {
166                 if (mService == null) {
167                     log("Binding service...");
168                     Intent intent = new Intent(IBluetoothPbap.class.getName());
169                     ComponentName comp = intent.resolveSystemService(
170                             mContext.getPackageManager(), 0);
171                     intent.setComponent(comp);
172                     if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
173                             UserHandle.CURRENT_OR_SELF)) {
174                         Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent);
175                         return false;
176                     }
177                 }
178             } catch (SecurityException se) {
179                 Log.e(TAG, "", se);
180                 return false;
181             }
182         }
183         return true;
184     }
185 
doUnbind()186     private void doUnbind() {
187         synchronized (mConnection) {
188             if (mService != null) {
189                 log("Unbinding service...");
190                 try {
191                     mContext.unbindService(mConnection);
192                 } catch (IllegalArgumentException ie) {
193                     Log.e(TAG, "", ie);
194                 } finally {
195                     mService = null;
196                 }
197             }
198         }
199     }
200 
201     /** @hide */
finalize()202     protected void finalize() throws Throwable {
203         try {
204             close();
205         } finally {
206             super.finalize();
207         }
208     }
209 
210     /**
211      * Close the connection to the backing service.
212      * Other public functions of BluetoothPbap will return default error
213      * results once close() has been called. Multiple invocations of close()
214      * are ok.
215      *
216      * @hide
217      */
close()218     public synchronized void close() {
219         IBluetoothManager mgr = mAdapter.getBluetoothManager();
220         if (mgr != null) {
221             try {
222                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
223             } catch (RemoteException re) {
224                 Log.e(TAG, "", re);
225             }
226         }
227         doUnbind();
228         mServiceListener = null;
229     }
230 
231     /**
232      * {@inheritDoc}
233      *
234      * @hide
235      */
236     @Override
237     @RequiresBluetoothConnectPermission
238     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
getConnectedDevices()239     public List<BluetoothDevice> getConnectedDevices() {
240         log("getConnectedDevices()");
241         final IBluetoothPbap service = mService;
242         if (service == null) {
243             Log.w(TAG, "Proxy not attached to service");
244             return new ArrayList<BluetoothDevice>();
245         }
246         try {
247             return Attributable.setAttributionSource(
248                     service.getConnectedDevices(mAttributionSource), mAttributionSource);
249         } catch (RemoteException e) {
250             Log.e(TAG, e.toString());
251         }
252         return new ArrayList<BluetoothDevice>();
253     }
254 
255     /**
256      * {@inheritDoc}
257      *
258      * @hide
259      */
260     @SystemApi
261     @Override
262     @RequiresBluetoothConnectPermission
263     @RequiresPermission(allOf = {
264             android.Manifest.permission.BLUETOOTH_CONNECT,
265             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
266     })
getConnectionState(@onNull BluetoothDevice device)267     public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
268         log("getConnectionState: device=" + device);
269         try {
270             final IBluetoothPbap service = mService;
271             if (service != null && isEnabled() && isValidDevice(device)) {
272                 return service.getConnectionState(device, mAttributionSource);
273             }
274             if (service == null) {
275                 Log.w(TAG, "Proxy not attached to service");
276             }
277             return BluetoothProfile.STATE_DISCONNECTED;
278         } catch (RemoteException e) {
279             Log.e(TAG, e.toString());
280         }
281         return BluetoothProfile.STATE_DISCONNECTED;
282     }
283 
284     /**
285      * {@inheritDoc}
286      *
287      * @hide
288      */
289     @Override
290     @RequiresBluetoothConnectPermission
291     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
getDevicesMatchingConnectionStates(int[] states)292     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
293         log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states));
294         final IBluetoothPbap service = mService;
295         if (service == null) {
296             Log.w(TAG, "Proxy not attached to service");
297             return new ArrayList<BluetoothDevice>();
298         }
299         try {
300             return Attributable.setAttributionSource(
301                     service.getDevicesMatchingConnectionStates(states, mAttributionSource),
302                     mAttributionSource);
303         } catch (RemoteException e) {
304             Log.e(TAG, e.toString());
305         }
306         return new ArrayList<BluetoothDevice>();
307     }
308 
309     /**
310      * Set connection policy of the profile and tries to disconnect it if connectionPolicy is
311      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
312      *
313      * <p> The device should already be paired.
314      * Connection policy can be one of:
315      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
316      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
317      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
318      *
319      * @param device Paired bluetooth device
320      * @param connectionPolicy is the connection policy to set to for this profile
321      * @return true if connectionPolicy is set, false on error
322      *
323      * @hide
324      */
325     @SystemApi
326     @RequiresBluetoothConnectPermission
327     @RequiresPermission(allOf = {
328             android.Manifest.permission.BLUETOOTH_CONNECT,
329             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
330     })
setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)331     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
332             @ConnectionPolicy int connectionPolicy) {
333         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
334         try {
335             final IBluetoothPbap service = mService;
336             if (service != null && isEnabled()
337                     && isValidDevice(device)) {
338                 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
339                         && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
340                     return false;
341                 }
342                 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
343             }
344             if (service == null) Log.w(TAG, "Proxy not attached to service");
345             return false;
346         } catch (RemoteException e) {
347             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
348             return false;
349         }
350     }
351 
352     /**
353      * Disconnects the current Pbap client (PCE). Currently this call blocks,
354      * it may soon be made asynchronous. Returns false if this proxy object is
355      * not currently connected to the Pbap service.
356      *
357      * @hide
358      */
359     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
360     @RequiresBluetoothConnectPermission
361     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
disconnect(BluetoothDevice device)362     public boolean disconnect(BluetoothDevice device) {
363         log("disconnect()");
364         final IBluetoothPbap service = mService;
365         if (service == null) {
366             Log.w(TAG, "Proxy not attached to service");
367             return false;
368         }
369         try {
370             service.disconnect(device, mAttributionSource);
371             return true;
372         } catch (RemoteException e) {
373             Log.e(TAG, e.toString());
374         }
375         return false;
376     }
377 
378     @SuppressLint("AndroidFrameworkBluetoothPermission")
379     private final ServiceConnection mConnection = new ServiceConnection() {
380         public void onServiceConnected(ComponentName className, IBinder service) {
381             log("Proxy object connected");
382             mService = IBluetoothPbap.Stub.asInterface(service);
383             if (mServiceListener != null) {
384                 mServiceListener.onServiceConnected(BluetoothProfile.PBAP, BluetoothPbap.this);
385             }
386         }
387 
388         public void onServiceDisconnected(ComponentName className) {
389             log("Proxy object disconnected");
390             doUnbind();
391             if (mServiceListener != null) {
392                 mServiceListener.onServiceDisconnected(BluetoothProfile.PBAP);
393             }
394         }
395     };
396 
isEnabled()397     private boolean isEnabled() {
398         if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
399         return false;
400     }
401 
isValidDevice(BluetoothDevice device)402     private boolean isValidDevice(BluetoothDevice device) {
403         if (device == null) return false;
404 
405         if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
406         return false;
407     }
408 
log(String msg)409     private static void log(String msg) {
410         if (DBG) {
411             Log.d(TAG, msg);
412         }
413     }
414 }
415