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.util.rule.FailureWatcher; 38 import com.android.systemui.shared.system.QuickStepContract; 39 40 import org.junit.rules.TestRule; 41 import org.junit.runner.Description; 42 import org.junit.runners.model.Statement; 43 44 import java.lang.annotation.ElementType; 45 import java.lang.annotation.Retention; 46 import java.lang.annotation.RetentionPolicy; 47 import java.lang.annotation.Target; 48 import java.util.concurrent.CountDownLatch; 49 import java.util.concurrent.TimeUnit; 50 51 /** 52 * Test rule that allows executing a test with Quickstep on and then Quickstep off. 53 * The test should be annotated with @QuickstepOnOff. 54 */ 55 public class NavigationModeSwitchRule implements TestRule { 56 57 static final String TAG = "QuickStepOnOffRule"; 58 59 public enum Mode { 60 THREE_BUTTON, TWO_BUTTON, ZERO_BUTTON, ALL 61 } 62 63 // Annotation for tests that need to be run with quickstep enabled and disabled. 64 @Retention(RetentionPolicy.RUNTIME) 65 @Target(ElementType.METHOD) 66 public @interface NavigationModeSwitch { mode()67 Mode mode() default ALL; 68 } 69 70 private final LauncherInstrumentation mLauncher; 71 NavigationModeSwitchRule(LauncherInstrumentation launcher)72 public NavigationModeSwitchRule(LauncherInstrumentation launcher) { 73 mLauncher = launcher; 74 } 75 76 @Override apply(Statement base, Description description)77 public Statement apply(Statement base, Description description) { 78 if (TestHelpers.isInLauncherProcess() && 79 description.getAnnotation(NavigationModeSwitch.class) != null) { 80 Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode(); 81 return new Statement() { 82 private void assertTrue(String message, boolean condition) { 83 if(!condition) { 84 final AssertionError assertionError = new AssertionError(message); 85 FailureWatcher.onError(mLauncher.getDevice(), description, assertionError); 86 throw assertionError; 87 } 88 } 89 90 @Override 91 public void evaluate() throws Throwable { 92 mLauncher.enableDebugTracing(); 93 final Context context = getInstrumentation().getContext(); 94 final int currentInteractionMode = 95 LauncherInstrumentation.getCurrentInteractionMode(context); 96 final String prevOverlayPkg = 97 QuickStepContract.isGesturalMode(currentInteractionMode) 98 ? NAV_BAR_MODE_GESTURAL_OVERLAY 99 : QuickStepContract.isSwipeUpMode(currentInteractionMode) 100 ? NAV_BAR_MODE_2BUTTON_OVERLAY 101 : NAV_BAR_MODE_3BUTTON_OVERLAY; 102 final LauncherInstrumentation.NavigationModel originalMode = 103 mLauncher.getNavigationModel(); 104 try { 105 if (mode == ZERO_BUTTON || mode == ALL) { 106 evaluateWithZeroButtons(); 107 } 108 if (mode == TWO_BUTTON || mode == ALL) { 109 evaluateWithTwoButtons(); 110 } 111 if (mode == THREE_BUTTON || mode == ALL) { 112 evaluateWithThreeButtons(); 113 } 114 } catch (Exception e) { 115 Log.e(TAG, "Exception", e); 116 throw e; 117 } finally { 118 assertTrue("Couldn't set overlay", 119 setActiveOverlay(prevOverlayPkg, originalMode)); 120 } 121 } 122 123 private void evaluateWithThreeButtons() throws Throwable { 124 if (setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, 125 LauncherInstrumentation.NavigationModel.THREE_BUTTON)) { 126 base.evaluate(); 127 } 128 } 129 130 private void evaluateWithTwoButtons() throws Throwable { 131 if (setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY, 132 LauncherInstrumentation.NavigationModel.TWO_BUTTON)) { 133 base.evaluate(); 134 } 135 } 136 137 private void evaluateWithZeroButtons() throws Throwable { 138 if (setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, 139 LauncherInstrumentation.NavigationModel.ZERO_BUTTON)) { 140 base.evaluate(); 141 } 142 } 143 144 private boolean packageExists(String packageName) { 145 try { 146 PackageManager pm = getInstrumentation().getContext().getPackageManager(); 147 if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) { 148 return false; 149 } 150 } catch (PackageManager.NameNotFoundException e) { 151 return false; 152 } 153 return true; 154 } 155 156 private boolean setActiveOverlay(String overlayPackage, 157 LauncherInstrumentation.NavigationModel expectedMode) throws Exception { 158 if (!packageExists(overlayPackage)) { 159 Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist"); 160 return false; 161 } 162 163 setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY, 164 overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY); 165 setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY, 166 overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY); 167 setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY, 168 overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY); 169 170 if (currentSysUiNavigationMode() != expectedMode) { 171 final CountDownLatch latch = new CountDownLatch(1); 172 final Context targetContext = getInstrumentation().getTargetContext(); 173 final SysUINavigationMode.NavigationModeChangeListener listener = 174 newMode -> { 175 if (LauncherInstrumentation.getNavigationModel(newMode.resValue) 176 == expectedMode) { 177 latch.countDown(); 178 } 179 }; 180 final SysUINavigationMode sysUINavigationMode = 181 SysUINavigationMode.INSTANCE.get(targetContext); 182 targetContext.getMainExecutor().execute(() -> 183 sysUINavigationMode.addModeChangeListener(listener)); 184 latch.await(60, TimeUnit.SECONDS); 185 targetContext.getMainExecutor().execute(() -> 186 sysUINavigationMode.removeModeChangeListener(listener)); 187 assertTrue("Navigation mode didn't change to " + expectedMode, 188 currentSysUiNavigationMode() == expectedMode); 189 } 190 191 for (int i = 0; i != 100; ++i) { 192 if (mLauncher.getNavigationModel() == expectedMode) break; 193 Thread.sleep(100); 194 } 195 assertTrue("Couldn't switch to " + overlayPackage, 196 mLauncher.getNavigationModel() == expectedMode); 197 198 for (int i = 0; i != 100; ++i) { 199 if (mLauncher.getNavigationModeMismatchError() == null) break; 200 Thread.sleep(100); 201 } 202 final String error = mLauncher.getNavigationModeMismatchError(); 203 assertTrue("Switching nav mode: " + error, error == null); 204 205 Thread.sleep(5000); 206 return true; 207 } 208 209 private void setOverlayPackageEnabled(String overlayPackage, boolean enable) 210 throws Exception { 211 Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable); 212 final String action = enable ? "enable" : "disable"; 213 UiDevice.getInstance(getInstrumentation()).executeShellCommand( 214 "cmd overlay " + action + " " + overlayPackage); 215 } 216 }; 217 } else { 218 return base; 219 } 220 } 221 222 private static LauncherInstrumentation.NavigationModel currentSysUiNavigationMode() { 223 return LauncherInstrumentation.getNavigationModel( 224 SysUINavigationMode.getMode( 225 getInstrumentation(). 226 getTargetContext()). 227 resValue); 228 } 229 } 230