1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.android.quickstep; 18 19 import static androidx.test.InstrumentationRegistry.getInstrumentation; 20 21 import static com.android.quickstep.NavigationModeSwitchRule.Mode.ALL; 22 import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON; 23 import static com.android.quickstep.NavigationModeSwitchRule.Mode.TWO_BUTTON; 24 import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON; 25 import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_2BUTTON_OVERLAY; 26 import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_3BUTTON_OVERLAY; 27 import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY; 28 29 import android.content.Context; 30 import android.content.pm.PackageManager; 31 import android.util.Log; 32 33 import androidx.test.uiautomator.UiDevice; 34 35 import com.android.launcher3.tapl.LauncherInstrumentation; 36 import com.android.launcher3.tapl.TestHelpers; 37 import com.android.launcher3.ui.AbstractLauncherUiTest; 38 import com.android.launcher3.util.Wait; 39 import com.android.launcher3.util.rule.FailureWatcher; 40 import com.android.systemui.shared.system.QuickStepContract; 41 42 import org.junit.rules.TestRule; 43 import org.junit.runner.Description; 44 import org.junit.runners.model.Statement; 45 46 import java.lang.annotation.ElementType; 47 import java.lang.annotation.Retention; 48 import java.lang.annotation.RetentionPolicy; 49 import java.lang.annotation.Target; 50 import java.util.concurrent.CountDownLatch; 51 import java.util.concurrent.TimeUnit; 52 53 /** 54 * Test rule that allows executing a test with Quickstep on and then Quickstep off. 55 * The test should be annotated with @QuickstepOnOff. 56 */ 57 public class NavigationModeSwitchRule implements TestRule { 58 59 static final String TAG = "QuickStepOnOffRule"; 60 61 public static final int WAIT_TIME_MS = 10000; 62 63 public enum Mode { 64 THREE_BUTTON, TWO_BUTTON, ZERO_BUTTON, ALL 65 } 66 67 // Annotation for tests that need to be run with quickstep enabled and disabled. 68 @Retention(RetentionPolicy.RUNTIME) 69 @Target(ElementType.METHOD) 70 public @interface NavigationModeSwitch { mode()71 Mode mode() default ALL; 72 } 73 74 private final LauncherInstrumentation mLauncher; 75 76 static final SysUINavigationMode SYS_UI_NAVIGATION_MODE = 77 SysUINavigationMode.INSTANCE.get(getInstrumentation().getTargetContext()); 78 NavigationModeSwitchRule(LauncherInstrumentation launcher)79 public NavigationModeSwitchRule(LauncherInstrumentation launcher) { 80 mLauncher = launcher; 81 } 82 83 @Override apply(Statement base, Description description)84 public Statement apply(Statement base, Description description) { 85 if (TestHelpers.isInLauncherProcess() && 86 description.getAnnotation(NavigationModeSwitch.class) != null) { 87 Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode(); 88 return new Statement() { 89 @Override 90 public void evaluate() throws Throwable { 91 mLauncher.enableDebugTracing(); 92 final Context context = getInstrumentation().getContext(); 93 final int currentInteractionMode = 94 LauncherInstrumentation.getCurrentInteractionMode(context); 95 final String prevOverlayPkg = getCurrentOverlayPackage(currentInteractionMode); 96 final LauncherInstrumentation.NavigationModel originalMode = 97 mLauncher.getNavigationModel(); 98 try { 99 if (mode == ZERO_BUTTON || mode == ALL) { 100 evaluateWithZeroButtons(); 101 } 102 if (mode == TWO_BUTTON || mode == ALL) { 103 evaluateWithTwoButtons(); 104 } 105 if (mode == THREE_BUTTON || mode == ALL) { 106 evaluateWithThreeButtons(); 107 } 108 } catch (Throwable e) { 109 Log.e(TAG, "Error", e); 110 throw e; 111 } finally { 112 Log.d(TAG, "In Finally block"); 113 assertTrue(mLauncher, "Couldn't set overlay", 114 setActiveOverlay(mLauncher, prevOverlayPkg, originalMode, 115 description), description); 116 } 117 } 118 119 private void evaluateWithThreeButtons() throws Throwable { 120 if (setActiveOverlay(mLauncher, NAV_BAR_MODE_3BUTTON_OVERLAY, 121 LauncherInstrumentation.NavigationModel.THREE_BUTTON, description)) { 122 base.evaluate(); 123 } 124 } 125 126 private void evaluateWithTwoButtons() throws Throwable { 127 if (setActiveOverlay(mLauncher, NAV_BAR_MODE_2BUTTON_OVERLAY, 128 LauncherInstrumentation.NavigationModel.TWO_BUTTON, description)) { 129 base.evaluate(); 130 } 131 } 132 133 private void evaluateWithZeroButtons() throws Throwable { 134 if (setActiveOverlay(mLauncher, NAV_BAR_MODE_GESTURAL_OVERLAY, 135 LauncherInstrumentation.NavigationModel.ZERO_BUTTON, description)) { 136 base.evaluate(); 137 } 138 } 139 }; 140 } else { 141 return base; 142 } 143 } 144 145 public static String getCurrentOverlayPackage(int currentInteractionMode) { 146 return QuickStepContract.isGesturalMode(currentInteractionMode) 147 ? NAV_BAR_MODE_GESTURAL_OVERLAY 148 : QuickStepContract.isSwipeUpMode(currentInteractionMode) 149 ? NAV_BAR_MODE_2BUTTON_OVERLAY 150 : NAV_BAR_MODE_3BUTTON_OVERLAY; 151 } 152 153 private static LauncherInstrumentation.NavigationModel currentSysUiNavigationMode() { 154 return LauncherInstrumentation.getNavigationModel( 155 SysUINavigationMode.getMode( 156 getInstrumentation(). 157 getTargetContext()). 158 resValue); 159 } 160 161 public static boolean setActiveOverlay(LauncherInstrumentation launcher, String overlayPackage, 162 LauncherInstrumentation.NavigationModel expectedMode, Description description) 163 throws Exception { 164 if (!packageExists(overlayPackage)) { 165 Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist"); 166 return false; 167 } 168 169 Log.d(TAG, "setActiveOverlay: " + overlayPackage + "..."); 170 UiDevice.getInstance(getInstrumentation()).executeShellCommand( 171 "cmd overlay enable-exclusive --category " + overlayPackage); 172 173 if (currentSysUiNavigationMode() != expectedMode) { 174 final CountDownLatch latch = new CountDownLatch(1); 175 final Context targetContext = getInstrumentation().getTargetContext(); 176 final SysUINavigationMode.NavigationModeChangeListener listener = 177 newMode -> { 178 if (LauncherInstrumentation.getNavigationModel(newMode.resValue) 179 == expectedMode) { 180 latch.countDown(); 181 } 182 }; 183 targetContext.getMainExecutor().execute(() -> 184 SYS_UI_NAVIGATION_MODE.addModeChangeListener(listener)); 185 latch.await(60, TimeUnit.SECONDS); 186 targetContext.getMainExecutor().execute(() -> 187 SYS_UI_NAVIGATION_MODE.removeModeChangeListener(listener)); 188 189 assertTrue(launcher, "Navigation mode didn't change to " + expectedMode, 190 currentSysUiNavigationMode() == expectedMode, description); 191 192 } 193 194 Wait.atMost("Couldn't switch to " + overlayPackage, 195 () -> launcher.getNavigationModel() == expectedMode, WAIT_TIME_MS, launcher); 196 197 Wait.atMost(() -> "Switching nav mode: " 198 + launcher.getNavigationModeMismatchError(false), 199 () -> launcher.getNavigationModeMismatchError(false) == null, 200 WAIT_TIME_MS, launcher); 201 AbstractLauncherUiTest.checkDetectedLeaks(launcher); 202 return true; 203 } 204 205 private static boolean packageExists(String packageName) { 206 try { 207 PackageManager pm = getInstrumentation().getContext().getPackageManager(); 208 if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) { 209 return false; 210 } 211 } catch (PackageManager.NameNotFoundException e) { 212 return false; 213 } 214 return true; 215 } 216 217 private static void assertTrue(LauncherInstrumentation launcher, String message, 218 boolean condition, Description description) { 219 launcher.checkForAnomaly(true, true); 220 if (!condition) { 221 final AssertionError assertionError = new AssertionError(message); 222 if (description != null) { 223 FailureWatcher.onError(launcher.getDevice(), description, assertionError); 224 } 225 throw assertionError; 226 } 227 } 228 } 229