1 /* 2 * Copyright (C) 2020 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.location.cts.common; 18 19 import android.app.UiAutomation; 20 import android.content.Context; 21 import android.content.pm.ApplicationInfo; 22 import android.content.pm.PackageManager; 23 import android.net.ConnectivityManager; 24 import android.net.NetworkInfo; 25 import android.provider.Settings; 26 import android.util.Log; 27 28 import androidx.test.InstrumentationRegistry; 29 30 import com.android.compatibility.common.util.SystemUtil; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.concurrent.Callable; 35 import java.util.concurrent.CountDownLatch; 36 import java.util.concurrent.TimeUnit; 37 38 public class TestUtils { 39 private static final String TAG = "LocationTestUtils"; 40 41 private static final long STANDARD_WAIT_TIME_MS = 50; 42 private static final long STANDARD_SLEEP_TIME_MS = 50; 43 44 private static final int DATA_CONNECTION_CHECK_INTERVAL_MS = 500; 45 private static final int DATA_CONNECTION_CHECK_COUNT = 10; // 500 * 10 - Roughly 5 secs wait 46 waitFor(CountDownLatch latch, int timeInSec)47 public static boolean waitFor(CountDownLatch latch, int timeInSec) throws InterruptedException { 48 // Since late 2014, if the main thread has been occupied for long enough, Android will 49 // increase its priority. Such new behavior can causes starvation to the background thread - 50 // even if the main thread has called await() to yield its execution, the background thread 51 // still can't get scheduled. 52 // 53 // Here we're trying to wait on the main thread for a PendingIntent from a background 54 // thread. Because of the starvation problem, the background thread may take up to 5 minutes 55 // to deliver the PendingIntent if we simply call await() on the main thread. In order to 56 // give the background thread a chance to run, we call Thread.sleep() in a loop. Such dirty 57 // hack isn't ideal, but at least it can work. 58 // 59 // See also: b/17423027 60 long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) / 61 (STANDARD_WAIT_TIME_MS + STANDARD_SLEEP_TIME_MS); 62 for (int i = 0; i < waitTimeRounds; ++i) { 63 Thread.sleep(STANDARD_SLEEP_TIME_MS); 64 if (latch.await(STANDARD_WAIT_TIME_MS, TimeUnit.MILLISECONDS)) { 65 return true; 66 } 67 } 68 return false; 69 } 70 waitForWithCondition(int timeInSec, Callable<Boolean> callback)71 public static boolean waitForWithCondition(int timeInSec, Callable<Boolean> callback) 72 throws Exception { 73 long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) / STANDARD_SLEEP_TIME_MS; 74 for (int i = 0; i < waitTimeRounds; ++i) { 75 Thread.sleep(STANDARD_SLEEP_TIME_MS); 76 if(callback.call()) return true; 77 } 78 return false; 79 } 80 deviceHasGpsFeature(Context context)81 public static boolean deviceHasGpsFeature(Context context) { 82 // If device does not have a GPS, skip the test. 83 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS)) { 84 return true; 85 } 86 Log.w(TAG, "GPS feature not present on device, skipping GPS test."); 87 return false; 88 } 89 90 /** 91 * Returns whether the device is currently connected to a wifi or cellular. 92 * 93 * @param context {@link Context} object 94 * @return {@code true} if connected to Wifi or Cellular; {@code false} otherwise 95 */ isConnectedToWifiOrCellular(Context context)96 public static boolean isConnectedToWifiOrCellular(Context context) { 97 NetworkInfo info = getActiveNetworkInfo(context); 98 return info != null 99 && info.isConnected() 100 && (info.getType() == ConnectivityManager.TYPE_WIFI 101 || info.getType() == ConnectivityManager.TYPE_MOBILE); 102 } 103 104 /** 105 * Gets the active network info. 106 * 107 * @param context {@link Context} object 108 * @return {@link NetworkInfo} 109 */ getActiveNetworkInfo(Context context)110 private static NetworkInfo getActiveNetworkInfo(Context context) { 111 ConnectivityManager cm = getConnectivityManager(context); 112 if (cm != null) { 113 return cm.getActiveNetworkInfo(); 114 } 115 return null; 116 } 117 118 /** Reads the {@code assisted_gps_enabled} flag. */ getAssistedGpsEnabled(Context context)119 public static int getAssistedGpsEnabled(Context context) { 120 int value = 121 Settings.Global.getInt( 122 context.getContentResolver(), Settings.Global.ASSISTED_GPS_ENABLED, 0); 123 Log.d(TAG, "Reading assisted_gps_enabled=" + value); 124 return value; 125 } 126 127 /** Overrides the {@code assisted_gps_enabled} flag. */ setAssistedGpsEnabled(Context context, int value)128 public static void setAssistedGpsEnabled(Context context, int value) { 129 int oldValue = getAssistedGpsEnabled(context); 130 if (oldValue != value) { 131 Log.i(TAG, "Setting assisted_gps_enabled as " + value); 132 SystemUtil.runWithShellPermissionIdentity( 133 () -> { 134 Settings.Global.putInt( 135 context.getContentResolver(), 136 Settings.Global.ASSISTED_GPS_ENABLED, 137 value); 138 }); 139 } 140 } 141 142 /** 143 * Gets the connectivity manager. 144 * 145 * @param context {@link Context} object 146 * @return {@link ConnectivityManager} 147 */ getConnectivityManager(Context context)148 public static ConnectivityManager getConnectivityManager(Context context) { 149 return (ConnectivityManager) context.getApplicationContext() 150 .getSystemService(Context.CONNECTIVITY_SERVICE); 151 } 152 153 /** 154 * Returns {@code true} if the setting {@code airplane_mode_on} is set to 1. 155 */ isAirplaneModeOn()156 public static boolean isAirplaneModeOn() { 157 return SystemUtil.runShellCommand("settings get global airplane_mode_on") 158 .trim().equals("1"); 159 } 160 161 /** 162 * Changes the setting {@code airplane_mode_on} to 1 if {@code enableAirplaneMode} 163 * is {@code true}. Otherwise, it is set to 0. 164 * 165 * <p>Waits for a certain time duration for network connections to turn on/off based on 166 * {@code enableAirplaneMode}. 167 */ setAirplaneModeOn(Context context, boolean enableAirplaneMode)168 public static void setAirplaneModeOn(Context context, 169 boolean enableAirplaneMode) throws InterruptedException { 170 Log.i(TAG, "Setting airplane_mode_on to " + enableAirplaneMode); 171 SystemUtil.runShellCommand("cmd connectivity airplane-mode " 172 + (enableAirplaneMode ? "enable" : "disable")); 173 174 // Wait for a few seconds until the airplane mode changes take effect. The airplane mode on 175 // state and the WiFi/cell connected state are opposite. So, we wait while they are the 176 // same or until the specified time interval expires. 177 // 178 // Note that in unusual cases where the WiFi/cell are not in a connected state before 179 // turning on airplane mode, then turning off airplane mode won't restore either of 180 // these connections, and then the wait time below will be wasteful. 181 int dataConnectionCheckCount = DATA_CONNECTION_CHECK_COUNT; 182 while (enableAirplaneMode == isConnectedToWifiOrCellular(context)) { 183 if (--dataConnectionCheckCount <= 0) { 184 Log.w(TAG, "Airplane mode " + (enableAirplaneMode ? "on" : "off") 185 + " setting did not take effect on WiFi/cell connected state."); 186 return; 187 } 188 Thread.sleep(DATA_CONNECTION_CHECK_INTERVAL_MS); 189 } 190 } 191 getPackagesWithPermissions(String permission)192 public static List<String> getPackagesWithPermissions(String permission) { 193 UiAutomation uiAnimation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 194 Context context = InstrumentationRegistry.getTargetContext(); 195 PackageManager pm = context.getPackageManager(); 196 197 ArrayList<String> packagesWithPermission = new ArrayList<>(); 198 List<ApplicationInfo> packages = pm.getInstalledApplications(/*flags=*/0); 199 200 for (ApplicationInfo applicationInfo : packages) { 201 String packageName = applicationInfo.packageName; 202 if (packageName.equals(context.getPackageName())) { 203 // Don't include this test package. 204 continue; 205 } 206 207 if (pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED) { 208 final int flags; 209 uiAnimation.adoptShellPermissionIdentity( 210 "android.permission.GET_RUNTIME_PERMISSIONS"); 211 try { 212 flags = pm.getPermissionFlags( 213 permission, packageName, android.os.Process.myUserHandle()); 214 } finally { 215 uiAnimation.dropShellPermissionIdentity(); 216 } 217 218 final boolean fixed = 219 (flags 220 & (PackageManager.FLAG_PERMISSION_USER_FIXED 221 | PackageManager.FLAG_PERMISSION_POLICY_FIXED 222 | PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) 223 != 0; 224 if (!fixed) { 225 packagesWithPermission.add(packageName); 226 } 227 } 228 } 229 return packagesWithPermission; 230 } 231 revokePermissions(String permission)232 public static List<String> revokePermissions(String permission) { 233 UiAutomation uiAnimation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 234 List<String> packages = getPackagesWithPermissions(permission); 235 for (String packageWithPermission : packages) { 236 Log.i(TAG, "Revoking permissions from: " + packageWithPermission); 237 uiAnimation.revokeRuntimePermission(packageWithPermission, permission); 238 } 239 return packages; 240 } 241 grantLocationPermissions(String permission, List<String> packages)242 public static void grantLocationPermissions(String permission, List<String> packages) { 243 UiAutomation uiAnimation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 244 for (String packageToGivePermission : packages) { 245 Log.i(TAG, "Granting permissions (back) to: " + packageToGivePermission); 246 uiAnimation.grantRuntimePermission(packageToGivePermission, permission); 247 } 248 } 249 } 250