• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 com.android.bedstead.nene.bluetooth;
18 
19 import static android.os.Build.VERSION_CODES.R;
20 import static android.os.Process.BLUETOOTH_UID;
21 
22 import static com.android.bedstead.nene.permissions.CommonPermissions.BLUETOOTH;
23 import static com.android.bedstead.nene.permissions.CommonPermissions.BLUETOOTH_CONNECT;
24 import static com.android.bedstead.nene.permissions.CommonPermissions.BLUETOOTH_PRIVILEGED;
25 import static com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL;
26 import static com.android.bedstead.nene.permissions.CommonPermissions.NETWORK_SETTINGS;
27 import static com.android.bedstead.nene.utils.Versions.T;
28 
29 import android.bluetooth.BluetoothAdapter;
30 import android.bluetooth.BluetoothManager;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageManager;
35 import android.os.UserHandle;
36 
37 import com.android.bedstead.nene.TestApis;
38 import com.android.bedstead.nene.annotations.Experimental;
39 import com.android.bedstead.nene.exceptions.NeneException;
40 import com.android.bedstead.nene.permissions.PermissionContext;
41 import com.android.bedstead.nene.utils.Poll;
42 import com.android.bedstead.nene.utils.Versions;
43 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
44 
45 /** Test APIs related to bluetooth. */
46 public final class Bluetooth {
47     public static final Bluetooth sInstance = new Bluetooth();
48 
49     private static final Context sContext = TestApis.context().instrumentedContext();
50     private static final BluetoothManager sBluetoothManager =
51             sContext.getSystemService(BluetoothManager.class);
52     private static final BluetoothAdapter sBluetoothAdapter = sBluetoothManager.getAdapter();
53 
Bluetooth()54     private Bluetooth() {}
55 
56     /** Enable or disable bluetooth on the device. */
setEnabled(boolean enabled)57     public void setEnabled(boolean enabled) {
58         if (isEnabled() == enabled) {
59             return;
60         }
61 
62         if (enabled) {
63             enable();
64         } else {
65             disable();
66         }
67     }
68 
enable()69     private void enable() {
70         try (PermissionContext p = TestApis.permissions()
71                                            .withPermission(BLUETOOTH_CONNECT,
72                                                    INTERACT_ACROSS_USERS_FULL, BLUETOOTH_PRIVILEGED)
73                                            .withPermissionOnVersionAtLeast(T, NETWORK_SETTINGS)) {
74             BlockingBroadcastReceiver r =
75                     BlockingBroadcastReceiver
76                             .create(sContext, BluetoothAdapter.ACTION_STATE_CHANGED,
77                                     this::isStateEnabled)
78                             .register();
79 
80             try {
81                 boolean returnValue = sBluetoothAdapter.enable();
82 
83                 r.awaitForBroadcast();
84                 Poll.forValue("Bluetooth Enabled", this::isEnabled)
85                         .toBeEqualTo(true)
86                         .errorOnFail("Waited for bluetooth to be enabled."
87                                 + " .enable() returned " + returnValue)
88                         .await();
89             } finally {
90                 r.unregisterQuietly();
91             }
92         }
93     }
94 
disable()95     private void disable() {
96         try (PermissionContext p = TestApis.permissions()
97                                            .withPermission(BLUETOOTH_CONNECT,
98                                                    INTERACT_ACROSS_USERS_FULL, BLUETOOTH_PRIVILEGED)
99                                            .withPermissionOnVersionAtLeast(T, NETWORK_SETTINGS)) {
100             BlockingBroadcastReceiver r =
101                     BlockingBroadcastReceiver
102                             .create(sContext, BluetoothAdapter.ACTION_STATE_CHANGED,
103                                     this::isStateDisabled)
104                             .register();
105 
106             try {
107                 boolean returnValue = sBluetoothAdapter.disable();
108 
109                 r.awaitForBroadcast();
110                 Poll.forValue("Bluetooth Enabled", this::isEnabled)
111                         .toBeEqualTo(false)
112                         .errorOnFail("Waited for bluetooth to be disabled."
113                                 + " .disable() returned " + returnValue)
114                         .await();
115             } finally {
116                 r.unregisterQuietly();
117             }
118         }
119     }
120 
121     /** {@code true} if bluetooth is enabled. */
isEnabled()122     public boolean isEnabled() {
123         try (PermissionContext p =
124                         TestApis.permissions().withPermissionOnVersionAtMost(R, BLUETOOTH)) {
125             return sBluetoothAdapter.isEnabled();
126         }
127     }
128 
isStateEnabled(Intent intent)129     private boolean isStateEnabled(Intent intent) {
130         return intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_ON;
131     }
132 
isStateDisabled(Intent intent)133     private boolean isStateDisabled(Intent intent) {
134         return intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_OFF;
135     }
136 
137     /** The bluetooth UID is associated with multiple packages. Get the main one. */
138     @Experimental
findPackageName()139     public String findPackageName() {
140         if (!Versions.meetsMinimumSdkVersionRequirement(T)) {
141             return "com.android.bluetooth";
142         }
143         // this activity will always be in the package where the rest of Bluetooth lives
144         var sentinelActivity = "com.android.bluetooth.opp.BluetoothOppLauncherActivity";
145         var packageManager = sContext.createContextAsUser(UserHandle.SYSTEM, 0).getPackageManager();
146         var allPackages = packageManager.getPackagesForUid(BLUETOOTH_UID);
147         String matchedPackage = null;
148         for (String candidatePackage : allPackages) {
149             PackageInfo packageInfo;
150             try {
151                 packageInfo =
152                         packageManager.getPackageInfo(
153                                 candidatePackage,
154                                 PackageManager.GET_ACTIVITIES
155                                         | PackageManager.MATCH_ANY_USER
156                                         | PackageManager.MATCH_UNINSTALLED_PACKAGES
157                                         | PackageManager.MATCH_DISABLED_COMPONENTS);
158             } catch (PackageManager.NameNotFoundException e) {
159                 // rethrow
160                 throw new NeneException(e);
161             }
162             if (packageInfo.activities == null) {
163                 continue;
164             }
165             for (var activity : packageInfo.activities) {
166                 if (sentinelActivity.equals(activity.name)) {
167                     if (matchedPackage == null) {
168                         matchedPackage = candidatePackage;
169                     } else {
170                         throw new NeneException("multiple main bluetooth packages found");
171                     }
172                 }
173             }
174         }
175         if (matchedPackage != null) {
176             return matchedPackage;
177         }
178         throw new NeneException("Could not find main bluetooth package");
179     }
180 }
181