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