content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
   * use this file except in compliance with the License. You may obtain a copy of
   * the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   * License for the specific language governing permissions and limitations under
   * the License.
   */
  
  package com.android.quickstep;
  
  import static androidx.test.InstrumentationRegistry.getInstrumentation;
  
  import static com.android.quickstep.NavigationModeSwitchRule.Mode.ALL;
  import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
  import static com.android.quickstep.NavigationModeSwitchRule.Mode.TWO_BUTTON;
  import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
  import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_2BUTTON_OVERLAY;
  import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_3BUTTON_OVERLAY;
  import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
  
  import android.content.Context;
  import android.content.pm.PackageManager;
  import android.util.Log;
  
  import androidx.test.uiautomator.UiDevice;
  
  import com.android.launcher3.tapl.LauncherInstrumentation;
  import com.android.launcher3.tapl.TestHelpers;
  import com.android.launcher3.util.rule.FailureWatcher;
  import com.android.systemui.shared.system.QuickStepContract;
  
  import org.junit.rules.TestRule;
  import org.junit.runner.Description;
  import org.junit.runners.model.Statement;
  
  import java.lang.annotation.ElementType;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.lang.annotation.Target;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.TimeUnit;
  
  /**
   * Test rule that allows executing a test with Quickstep on and then Quickstep off.
   * The test should be annotated with @QuickstepOnOff.
   */
  public class NavigationModeSwitchRule implements TestRule {
  
      static final String TAG = "QuickStepOnOffRule";
  
      public enum Mode {
          THREE_BUTTON, TWO_BUTTON, ZERO_BUTTON, ALL
      }
  
      // Annotation for tests that need to be run with quickstep enabled and disabled.
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface NavigationModeSwitch {
          Mode mode() default ALL;
      }
  
      private final LauncherInstrumentation mLauncher;
  
      public NavigationModeSwitchRule(LauncherInstrumentation launcher) {
          mLauncher = launcher;
      }
  
      @Override
      public Statement apply(Statement base, Description description) {
          if (TestHelpers.isInLauncherProcess() &&
                  description.getAnnotation(NavigationModeSwitch.class) != null) {
              Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
              return new Statement() {
                  private void assertTrue(String message, boolean condition) {
                      if(!condition) {
                          final AssertionError assertionError = new AssertionError(message);
                          FailureWatcher.onError(mLauncher.getDevice(), description, assertionError);
                          throw assertionError;
                      }
                  }
  
                  @Override
                  public void evaluate() throws Throwable {
                      mLauncher.enableDebugTracing();
                      final Context context = getInstrumentation().getContext();
                      final int currentInteractionMode =
                              LauncherInstrumentation.getCurrentInteractionMode(context);
                      final String prevOverlayPkg =
                              QuickStepContract.isGesturalMode(currentInteractionMode)
                                      ? NAV_BAR_MODE_GESTURAL_OVERLAY
                                      : QuickStepContract.isSwipeUpMode(currentInteractionMode)
                                              ? NAV_BAR_MODE_2BUTTON_OVERLAY
                                              : NAV_BAR_MODE_3BUTTON_OVERLAY;
                      final LauncherInstrumentation.NavigationModel originalMode =
                              mLauncher.getNavigationModel();
                      try {
                          if (mode == ZERO_BUTTON || mode == ALL) {
                              evaluateWithZeroButtons();
                          }
                          if (mode == TWO_BUTTON || mode == ALL) {
                              evaluateWithTwoButtons();
                          }
                          if (mode == THREE_BUTTON || mode == ALL) {
                              evaluateWithThreeButtons();
                          }
                      } catch (Exception e) {
                          Log.e(TAG, "Exception", e);
                          throw e;
                      } finally {
                          assertTrue("Couldn't set overlay",
                                  setActiveOverlay(prevOverlayPkg, originalMode));
                      }
                  }
  
                  private void evaluateWithThreeButtons() throws Throwable {
                      if (setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
                              LauncherInstrumentation.NavigationModel.THREE_BUTTON)) {
                          base.evaluate();
                      }
                  }
  
                  private void evaluateWithTwoButtons() throws Throwable {
                      if (setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
                              LauncherInstrumentation.NavigationModel.TWO_BUTTON)) {
                          base.evaluate();
                      }
                  }
  
                  private void evaluateWithZeroButtons() throws Throwable {
                      if (setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
                              LauncherInstrumentation.NavigationModel.ZERO_BUTTON)) {
                          base.evaluate();
                      }
                  }
  
                  private boolean packageExists(String packageName) {
                      try {
                          PackageManager pm = getInstrumentation().getContext().getPackageManager();
                          if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) {
                              return false;
                          }
                      } catch (PackageManager.NameNotFoundException e) {
                          return false;
                      }
                      return true;
                  }
  
                  private boolean setActiveOverlay(String overlayPackage,
                          LauncherInstrumentation.NavigationModel expectedMode) throws Exception {
                      if (!packageExists(overlayPackage)) {
                          Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist");
                          return false;
                      }
  
                      setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
                              overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
                      setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
                              overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
                      setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
                              overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
  
                      if (currentSysUiNavigationMode() != expectedMode) {
                          final CountDownLatch latch = new CountDownLatch(1);
                          final Context targetContext = getInstrumentation().getTargetContext();
                          final SysUINavigationMode.NavigationModeChangeListener listener =
                                  newMode -> {
                                      if (LauncherInstrumentation.getNavigationModel(newMode.resValue)
                                              == expectedMode) {
                                          latch.countDown();
                                      }
                                  };
                          final SysUINavigationMode sysUINavigationMode =
                                  SysUINavigationMode.INSTANCE.get(targetContext);
                          targetContext.getMainExecutor().execute(() ->
                                  sysUINavigationMode.addModeChangeListener(listener));
                          latch.await(60, TimeUnit.SECONDS);
                          targetContext.getMainExecutor().execute(() ->
                                  sysUINavigationMode.removeModeChangeListener(listener));
                          assertTrue("Navigation mode didn't change to " + expectedMode,
                                  currentSysUiNavigationMode() == expectedMode);
                      }
  
                      for (int i = 0; i != 100; ++i) {
                          if (mLauncher.getNavigationModel() == expectedMode) break;
                          Thread.sleep(100);
                      }
                      assertTrue("Couldn't switch to " + overlayPackage,
                              mLauncher.getNavigationModel() == expectedMode);
  
                      for (int i = 0; i != 100; ++i) {
                          if (mLauncher.getNavigationModeMismatchError() == null) break;
                          Thread.sleep(100);
                      }
                      final String error = mLauncher.getNavigationModeMismatchError();
                      assertTrue("Switching nav mode: " + error, error == null);
  
                      Thread.sleep(5000);
                      return true;
                  }
  
                  private void setOverlayPackageEnabled(String overlayPackage, boolean enable)
                          throws Exception {
                      Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
                      final String action = enable ? "enable" : "disable";
                      UiDevice.getInstance(getInstrumentation()).executeShellCommand(
                              "cmd overlay " + action + " " + overlayPackage);
                  }
              };
          } else {
              return base;
          }
      }
  
      private static LauncherInstrumentation.NavigationModel currentSysUiNavigationMode() {
          return LauncherInstrumentation.getNavigationModel(
                  SysUINavigationMode.getMode(
                          getInstrumentation().
                                  getTargetContext()).
                          resValue);
      }
  }
  