• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.platform.app.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.ZERO_BUTTON;
24 import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_3BUTTON_OVERLAY;
25 import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
26 
27 import android.content.Context;
28 import android.content.pm.PackageManager;
29 import android.os.Process;
30 import android.util.Log;
31 
32 import androidx.test.uiautomator.UiDevice;
33 
34 import com.android.launcher3.tapl.LauncherInstrumentation;
35 import com.android.launcher3.tapl.TestHelpers;
36 import com.android.launcher3.ui.AbstractLauncherUiTest;
37 import com.android.launcher3.util.DisplayController;
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 @NavigationModeSwitch.
56  */
57 public class NavigationModeSwitchRule implements TestRule {
58 
59     static final String TAG = "QuickStepOnOffRule";
60 
61     public enum Mode {
62         THREE_BUTTON, ZERO_BUTTON, ALL
63     }
64 
65     // Annotation for tests that need to be run with quickstep enabled and disabled.
66     @Retention(RetentionPolicy.RUNTIME)
67     @Target(ElementType.METHOD)
68     public @interface NavigationModeSwitch {
mode()69         Mode mode() default ALL;
70     }
71 
72     private final LauncherInstrumentation mLauncher;
73 
74     static final DisplayController DISPLAY_CONTROLLER =
75             DisplayController.INSTANCE.get(getInstrumentation().getTargetContext());
76 
NavigationModeSwitchRule(LauncherInstrumentation launcher)77     public NavigationModeSwitchRule(LauncherInstrumentation launcher) {
78         mLauncher = launcher;
79     }
80 
81     @Override
apply(Statement base, Description description)82     public Statement apply(Statement base, Description description) {
83         if (TestHelpers.isInLauncherProcess() &&
84                 description.getAnnotation(NavigationModeSwitch.class) != null) {
85             Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
86             return new Statement() {
87                 @Override
88                 public void evaluate() throws Throwable {
89                     mLauncher.enableDebugTracing();
90                     final Context context = getInstrumentation().getContext();
91                     final int currentInteractionMode =
92                             LauncherInstrumentation.getCurrentInteractionMode(context);
93                     final String prevOverlayPkg = getCurrentOverlayPackage(currentInteractionMode);
94                     final LauncherInstrumentation.NavigationModel originalMode =
95                             mLauncher.getNavigationModel();
96                     try {
97                         if (mode == ZERO_BUTTON || mode == ALL) {
98                             evaluateWithZeroButtons();
99                         }
100                         if (mode == THREE_BUTTON || mode == ALL) {
101                             evaluateWithThreeButtons();
102                         }
103                     } catch (Throwable e) {
104                         Log.e(TAG, "Error", e);
105                         throw e;
106                     } finally {
107                         Log.d(TAG, "In Finally block");
108                         assertTrue(mLauncher, "Couldn't set overlay",
109                                 setActiveOverlay(mLauncher, prevOverlayPkg, originalMode,
110                                         description), description);
111                     }
112                 }
113 
114                 private void evaluateWithThreeButtons() throws Throwable {
115                     if (setActiveOverlay(mLauncher, NAV_BAR_MODE_3BUTTON_OVERLAY,
116                             LauncherInstrumentation.NavigationModel.THREE_BUTTON, description)) {
117                         base.evaluate();
118                     }
119                 }
120 
121                 private void evaluateWithZeroButtons() throws Throwable {
122                     if (setActiveOverlay(mLauncher, NAV_BAR_MODE_GESTURAL_OVERLAY,
123                             LauncherInstrumentation.NavigationModel.ZERO_BUTTON, description)) {
124                         base.evaluate();
125                     }
126                 }
127             };
128         } else {
129             return base;
130         }
131     }
132 
133     public static String getCurrentOverlayPackage(int currentInteractionMode) {
134         return QuickStepContract.isGesturalMode(currentInteractionMode)
135                 ? NAV_BAR_MODE_GESTURAL_OVERLAY
136                 : NAV_BAR_MODE_3BUTTON_OVERLAY;
137     }
138 
139     private static LauncherInstrumentation.NavigationModel currentSysUiNavigationMode() {
140         return LauncherInstrumentation.getNavigationModel(
141                 DisplayController.getNavigationMode(
142                         getInstrumentation().
143                                 getTargetContext()).
144                         resValue);
145     }
146 
147     public static boolean setActiveOverlay(LauncherInstrumentation launcher, String overlayPackage,
148             LauncherInstrumentation.NavigationModel expectedMode, Description description)
149             throws Exception {
150         if (!packageExists(overlayPackage)) {
151             Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist");
152             return false;
153         }
154 
155         Log.d(TAG, "setActiveOverlay: " + overlayPackage + "...");
156         UiDevice.getInstance(getInstrumentation()).executeShellCommand(
157                 String.format("cmd overlay enable-exclusive --user %d --category %s",
158                         Process.myUserHandle().getIdentifier(),
159                         overlayPackage));
160 
161         if (currentSysUiNavigationMode() != expectedMode) {
162             final CountDownLatch latch = new CountDownLatch(1);
163             final Context targetContext = getInstrumentation().getTargetContext();
164             final DisplayController.DisplayInfoChangeListener listener =
165                     (context, info, flags) -> {
166                         if (LauncherInstrumentation.getNavigationModel(
167                                 info.getNavigationMode().resValue) == expectedMode) {
168                             latch.countDown();
169                         }
170                     };
171             targetContext.getMainExecutor().execute(() ->
172                     DISPLAY_CONTROLLER.addChangeListener(listener));
173             latch.await(60, TimeUnit.SECONDS);
174             targetContext.getMainExecutor().execute(() ->
175                     DISPLAY_CONTROLLER.removeChangeListener(listener));
176 
177             assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
178                     currentSysUiNavigationMode() == expectedMode, description);
179 
180         }
181 
182         Wait.atMost("Couldn't switch to " + overlayPackage,
183                 () -> launcher.getNavigationModel() == expectedMode,
184                 launcher);
185 
186         Wait.atMost(() -> "Switching nav mode: "
187                         + launcher.getNavigationModeMismatchError(false),
188                 () -> launcher.getNavigationModeMismatchError(false) == null,
189                 launcher);
190         AbstractLauncherUiTest.checkDetectedLeaks(launcher, false);
191         return true;
192     }
193 
194     private static boolean packageExists(String packageName) {
195         try {
196             PackageManager pm = getInstrumentation().getContext().getPackageManager();
197             if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) {
198                 return false;
199             }
200         } catch (PackageManager.NameNotFoundException e) {
201             return false;
202         }
203         return true;
204     }
205 
206     private static void assertTrue(LauncherInstrumentation launcher, String message,
207             boolean condition, Description description) {
208         launcher.checkForAnomaly(true, true);
209         if (!condition) {
210             if (description != null) {
211                 FailureWatcher.onError(launcher, description);
212             }
213             throw new AssertionError(message);
214         }
215     }
216 }
217