1 /* 2 * Copyright 2023 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 androidx.camera.testing.impl; 18 19 import static org.junit.Assume.assumeFalse; 20 import static org.junit.Assume.assumeTrue; 21 22 import android.annotation.SuppressLint; 23 import android.app.Activity; 24 import android.app.Instrumentation; 25 import android.app.KeyguardManager; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.hardware.camera2.CameraAccessException; 29 import android.hardware.camera2.CameraCharacteristics; 30 import android.os.Build; 31 import android.os.RemoteException; 32 import android.util.Log; 33 34 import androidx.camera.core.Logger; 35 import androidx.camera.testing.impl.activity.ForegroundTestActivity; 36 import androidx.test.espresso.Espresso; 37 import androidx.test.espresso.IdlingRegistry; 38 import androidx.test.espresso.idling.CountingIdlingResource; 39 import androidx.test.platform.app.InstrumentationRegistry; 40 import androidx.test.uiautomator.UiDevice; 41 42 import org.jspecify.annotations.NonNull; 43 import org.jspecify.annotations.Nullable; 44 import org.junit.AssumptionViolatedException; 45 46 import java.io.IOException; 47 48 /** Utility functions of tests on CoreTestApp. */ 49 public final class CoreAppTestUtil { 50 51 private static final String TAG = "CoreAppTestUtil"; 52 53 /** ADB shell input key code for dismissing keyguard for device with API level <= 22. */ 54 private static final int DISMISS_LOCK_SCREEN_CODE = 82; 55 /** ADB shell command for dismissing keyguard for device with API level >= 23. */ 56 private static final String ADB_SHELL_DISMISS_KEYGUARD_API23_AND_ABOVE = "wm dismiss-keyguard"; 57 /** ADB shell command to set the screen always on when usb is connected. */ 58 private static final String ADB_SHELL_SCREEN_ALWAYS_ON = "svc power stayon true"; 59 60 private static final int MAX_TIMEOUT_MS = 3000; 61 CoreAppTestUtil()62 private CoreAppTestUtil() { 63 } 64 65 /** 66 * Check if this is compatible device for test. 67 * 68 * <p> Most devices should be compatible except devices with compatible issues. 69 * 70 */ assumeCompatibleDevice()71 public static void assumeCompatibleDevice() { 72 // TODO(b/134894604) This will be removed once the issue is fixed. 73 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP 74 && Build.MODEL.contains("Nexus 5")) { 75 throw new AssumptionViolatedException("Known issue, b/134894604."); 76 } 77 78 assumeFalse("See b/152082918, Wembley Api30 has a libjpeg issue which causes" 79 + " the test failure.", 80 Build.MODEL.equalsIgnoreCase("wembley") && Build.VERSION.SDK_INT <= 30); 81 } 82 83 /** 84 * Throws the Exception for the devices which is not compatible to the testing. 85 */ assumeCanTestCameraDisconnect()86 public static void assumeCanTestCameraDisconnect() { 87 // TODO(b/141656413) Remove this when the issue is fixed. 88 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M 89 && (Build.MODEL.contains("Nexus 5") || Build.MODEL.contains("Pixel C"))) { 90 throw new AssumptionViolatedException("Known issue, b/141656413."); 91 } 92 } 93 94 /** 95 * Throws the Exception for devices whose front camera is not testable. 96 * 97 * Vivo 1805 will popup dialog to ask permission for accessing its front camera and then break 98 * the test. This function is used for switching camera tests that the tests should be 99 * skipped no matter the tests are started from the back or front camera. 100 */ assumeCanTestFrontCamera()101 public static void assumeCanTestFrontCamera() throws CameraAccessException { 102 if ("vivo 1805".equals(Build.MODEL)) { 103 throw new AssumptionViolatedException("Vivo 1805 will popup dialog to ask permission " 104 + "to access the front camera."); 105 } 106 } 107 108 /** 109 * Throws the Exception for devices which the specified camera id is front camera and is not 110 * testable. 111 * 112 * Vivo 1805 will popup dialog to ask permission for accessing its front camera and then break 113 * the test. 114 */ assumeNotUntestableFrontCamera(@onNull String cameraId)115 public static void assumeNotUntestableFrontCamera(@NonNull String cameraId) 116 throws CameraAccessException { 117 CameraCharacteristics characteristics = 118 CameraUtil.getCameraManager().getCameraCharacteristics(cameraId); 119 if ("vivo 1805".equals(Build.MODEL) && characteristics.get( 120 CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) { 121 throw new AssumptionViolatedException("Vivo 1805 will popup dialog to ask permission " 122 + "to access the front camera."); 123 } 124 } 125 126 /** 127 * Clean up the device UI and back to the home screen for test. 128 * @param instrumentation the instrumentation used to run the test 129 */ 130 @SuppressLint("MissingPermission") // Permission needed for action_close_system_dialogs in S 131 @SuppressWarnings("deprecation") clearDeviceUI(@onNull Instrumentation instrumentation)132 public static void clearDeviceUI(@NonNull Instrumentation instrumentation) { 133 UiDevice device = UiDevice.getInstance(instrumentation); 134 // On some devices, its necessary to wake up the device before attempting unlock, otherwise 135 // unlock attempt will not unlock. 136 try { 137 device.wakeUp(); 138 } catch (RemoteException remoteException) { 139 } 140 141 // In case the lock screen on top, the action to dismiss it. 142 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 143 device.pressKeyCode(DISMISS_LOCK_SCREEN_CODE); 144 } else { 145 try { 146 device.executeShellCommand(ADB_SHELL_DISMISS_KEYGUARD_API23_AND_ABOVE); 147 } catch (IOException e) { 148 } 149 } 150 151 try { 152 device.executeShellCommand(ADB_SHELL_SCREEN_ALWAYS_ON); 153 } catch (IOException e) { 154 } 155 156 device.pressHome(); 157 try { 158 device.waitForIdle(MAX_TIMEOUT_MS); 159 } catch (IllegalStateException e) { 160 Logger.d(TAG, "Fail to waitForIdle", e); 161 } 162 // Close system dialogs first to avoid interrupt. 163 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { 164 instrumentation.getTargetContext().sendBroadcast( 165 new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 166 } 167 } 168 169 /** 170 * Checks whether keyguard is locked. 171 * 172 * Keyguard is locked if the screen is off or the device is currently locked and requires a 173 * PIN, pattern or password to unlock. 174 * 175 * @throws IllegalStateException if keyguard is locked. 176 */ checkKeyguard(@onNull Context context)177 public static void checkKeyguard(@NonNull Context context) { 178 KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService( 179 Context.KEYGUARD_SERVICE); 180 181 if (keyguardManager != null && keyguardManager.isKeyguardLocked()) { 182 throw new IllegalStateException("<KEYGUARD_STATE_ERROR> Keyguard is locked!"); 183 } 184 } 185 186 /** 187 * Try to clear the UI and then check if there is any dialog or lock screen on the top of the 188 * window that might cause the activity related test fail. 189 * 190 * @param instrumentation The instrumentation instance. 191 * @throws ForegroundOccupiedError throw the exception when the test app cannot get 192 * foreground of the device window in CameraX lab. 193 */ prepareDeviceUI(@onNull Instrumentation instrumentation)194 public static void prepareDeviceUI(@NonNull Instrumentation instrumentation) 195 throws ForegroundOccupiedError { 196 clearDeviceUI(instrumentation); 197 198 ForegroundTestActivity activityRef = null; 199 try { 200 Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 201 202 Intent startIntent = new Intent(Intent.ACTION_MAIN); 203 startIntent.setClassName(context.getPackageName(), 204 ForegroundTestActivity.class.getName()); 205 startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 206 207 activityRef = ForegroundTestActivity.class.cast( 208 instrumentation.startActivitySync(startIntent)); 209 instrumentation.waitForIdleSync(); 210 211 if (activityRef == null) { 212 Logger.d(TAG, String.format("Activity %s, failed to launch", 213 startIntent.getComponent()) + ", ignore the foreground checking"); 214 return; 215 } 216 IdlingRegistry.getInstance().register(activityRef.getViewReadyIdlingResource()); 217 218 // The {@link Espresso#onIdle()} throws timeout exception if the 219 // ForegroundTestActivity cannot get focus. The default timeout in espresso is 26 sec. 220 Espresso.onIdle(); 221 return; 222 } catch (Exception e) { 223 Logger.d(TAG, "Fail to get foreground", e); 224 } finally { 225 if (activityRef != null) { 226 IdlingRegistry.getInstance().unregister(activityRef.getViewReadyIdlingResource()); 227 final Activity act = activityRef; 228 instrumentation.runOnMainSync(() -> act.finish()); 229 instrumentation.waitForIdleSync(); 230 } 231 } 232 233 // Throw AssumptionViolatedException to skip the test if not in the CameraX lab 234 // environment. The loggable tag will be set when running the CameraX daily testing. 235 assumeTrue(Log.isLoggable("MH", Log.DEBUG)); 236 237 throw new ForegroundOccupiedError("CameraX_fail_to_start_foreground, model:" + Build.MODEL); 238 } 239 240 /** The display foreground of the device is occupied that cannot execute UI related test. */ 241 public static class ForegroundOccupiedError extends Exception { ForegroundOccupiedError(@onNull String message)242 public ForegroundOccupiedError(@NonNull String message) { 243 super(message); 244 } 245 } 246 247 /** 248 * Launch activity and return the activity instance for testing. 249 * 250 * @param instrumentation The instrumentation used to run the test 251 * @param activityClass The activity under test. This must be a class in the instrumentation 252 * targetPackage specified in the AndroidManifest.xml 253 * @param startIntent The Intent that will be used to start the Activity under test. If 254 * {@code startIntent} is null, a default launch Intent for the 255 * {@code activityClass} is used. 256 * @param <T> The Activity class under test 257 * @return Returns the reference to the activity for test. 258 */ launchActivity( @onNull Instrumentation instrumentation, @NonNull Class<T> activityClass, @Nullable Intent startIntent)259 public static <T extends Activity> @Nullable T launchActivity( 260 @NonNull Instrumentation instrumentation, @NonNull Class<T> activityClass, 261 @Nullable Intent startIntent) { 262 Context context = instrumentation.getTargetContext(); 263 264 // inject custom intent, if provided 265 if (null == startIntent) { 266 startIntent = new Intent(Intent.ACTION_MAIN); 267 } 268 269 // Set target component if not set Intent 270 if (null == startIntent.getComponent()) { 271 startIntent.setClassName(context.getPackageName(), activityClass.getName()); 272 } 273 274 // Set launch flags where if not set Intent 275 if (0 /* No flags set */ == startIntent.getFlags()) { 276 startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 277 } 278 279 T activityRef = activityClass.cast(instrumentation.startActivitySync(startIntent)); 280 instrumentation.waitForIdleSync(); 281 282 return activityRef; 283 } 284 285 /** 286 * Launch a auto-closed {@link ForegroundTestActivity}. 287 * 288 * <p>The launched activity will make the callee activity enter PAUSE state and return to 289 * RESUME state after the activity is closed. 290 * 291 * <p>If the <code>countingIdlingResource</code> is specified, the activity will wait for it 292 * becoming idle to close the activity. This can be used to control the activity close speed to 293 * make it work more like the real user behavior. 294 */ launchAutoClosedForegroundActivity( @onNull Context context, @NonNull Instrumentation instrumentation, @Nullable CountingIdlingResource countingIdlingResource )295 public static void launchAutoClosedForegroundActivity( 296 @NonNull Context context, 297 @NonNull Instrumentation instrumentation, 298 @Nullable CountingIdlingResource countingIdlingResource 299 ) { 300 ForegroundTestActivity foregroundTestActivity = launchActivity( 301 instrumentation, 302 ForegroundTestActivity.class, 303 new Intent(context, ForegroundTestActivity.class) 304 ); 305 try { 306 if (countingIdlingResource != null) { 307 IdlingRegistry.getInstance().register(countingIdlingResource); 308 Espresso.onIdle(); 309 IdlingRegistry.getInstance().unregister(countingIdlingResource); 310 } 311 } finally { 312 foregroundTestActivity.finish(); 313 } 314 } 315 } 316