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.launcher3.allapps;
  
  import static com.android.launcher3.LauncherState.NORMAL;
  import static com.android.launcher3.LauncherState.OVERVIEW;
  import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.HOTSEAT;
  import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.PREDICTION;
  
  import android.animation.Animator;
  import android.animation.AnimatorInflater;
  import android.animation.AnimatorListenerAdapter;
  import android.content.SharedPreferences;
  import android.os.Handler;
  import android.view.MotionEvent;
  
  import com.android.launcher3.AbstractFloatingView;
  import com.android.launcher3.Launcher;
  import com.android.launcher3.LauncherState;
  import com.android.launcher3.LauncherStateManager;
  import com.android.launcher3.LauncherStateManager.StateListener;
  import com.android.launcher3.R;
  import com.android.launcher3.Utilities;
  import com.android.launcher3.compat.UserManagerCompat;
  import com.android.launcher3.states.InternalStateHandler;
  
  /**
   * Abstract base class of floating view responsible for showing discovery bounce animation
   */
  public class DiscoveryBounce extends AbstractFloatingView {
  
      private static final long DELAY_MS = 450;
  
      public static final String HOME_BOUNCE_SEEN = "launcher.apps_view_shown";
      public static final String SHELF_BOUNCE_SEEN = "launcher.shelf_bounce_seen";
      public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
      public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
  
      public static final int BOUNCE_MAX_COUNT = 3;
  
      private final Launcher mLauncher;
      private final Animator mDiscoBounceAnimation;
  
      private final StateListener mStateListener = new StateListener() {
          @Override
          public void onStateTransitionStart(LauncherState toState) {
              handleClose(false);
          }
  
          @Override
          public void onStateTransitionComplete(LauncherState finalState) {}
      };
  
      public DiscoveryBounce(Launcher launcher, float delta) {
          super(launcher, null);
          mLauncher = launcher;
          AllAppsTransitionController controller = mLauncher.getAllAppsController();
  
          mDiscoBounceAnimation =
                  AnimatorInflater.loadAnimator(launcher, R.animator.discovery_bounce);
          mDiscoBounceAnimation.setTarget(new VerticalProgressWrapper(controller, delta));
          mDiscoBounceAnimation.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationEnd(Animator animation) {
                  handleClose(false);
              }
          });
          mDiscoBounceAnimation.addListener(controller.getProgressAnimatorListener());
          launcher.getStateManager().addStateListener(mStateListener);
      }
  
      @Override
      protected void onAttachedToWindow() {
          super.onAttachedToWindow();
          mDiscoBounceAnimation.start();
      }
  
      @Override
      protected void onDetachedFromWindow() {
          super.onDetachedFromWindow();
          if (mDiscoBounceAnimation.isRunning()) {
              mDiscoBounceAnimation.end();
          }
      }
  
      @Override
      public boolean onBackPressed() {
          super.onBackPressed();
          // Go back to the previous state (from a user's perspective this floating view isn't
          // something to go back from).
          return false;
      }
  
      @Override
      public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
          handleClose(false);
          return false;
      }
  
      @Override
      protected void handleClose(boolean animate) {
          if (mIsOpen) {
              mIsOpen = false;
              mLauncher.getDragLayer().removeView(this);
              // Reset the all-apps progress to what ever it was previously.
              mLauncher.getAllAppsController().setProgress(mLauncher.getStateManager()
                      .getState().getVerticalProgress(mLauncher));
              mLauncher.getStateManager().removeStateListener(mStateListener);
          }
      }
  
      @Override
      public void logActionCommand(int command) {
          // Since this is on-boarding popup, it is not a user controlled action.
      }
  
      @Override
      protected boolean isOfType(int type) {
          return (type & TYPE_DISCOVERY_BOUNCE) != 0;
      }
  
      private void show(int containerType) {
          mIsOpen = true;
          mLauncher.getDragLayer().addView(this);
          mLauncher.getUserEventDispatcher().logActionBounceTip(containerType);
      }
  
      public static void showForHomeIfNeeded(Launcher launcher) {
          showForHomeIfNeeded(launcher, true);
      }
  
      private static void showForHomeIfNeeded(Launcher launcher, boolean withDelay) {
          if (!launcher.isInState(NORMAL)
                  || (launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)
                  && !shouldShowForWorkProfile(launcher))
                  || AbstractFloatingView.getTopOpenView(launcher) != null
                  || UserManagerCompat.getInstance(launcher).isDemoUser()
                  || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
              return;
          }
  
          if (withDelay) {
              new Handler().postDelayed(() -> showForHomeIfNeeded(launcher, false), DELAY_MS);
              return;
          }
          incrementHomeBounceCount(launcher);
  
          new DiscoveryBounce(launcher, 0).show(HOTSEAT);
      }
  
      public static void showForOverviewIfNeeded(Launcher launcher) {
          showForOverviewIfNeeded(launcher, true);
      }
  
      private static void showForOverviewIfNeeded(Launcher launcher, boolean withDelay) {
          if (!launcher.isInState(OVERVIEW)
                  || !launcher.hasBeenResumed()
                  || launcher.isForceInvisible()
                  || launcher.getDeviceProfile().isVerticalBarLayout()
                  || (launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)
                  && !shouldShowForWorkProfile(launcher))
                  || UserManagerCompat.getInstance(launcher).isDemoUser()
                  || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
              return;
          }
  
          if (withDelay) {
              new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false), DELAY_MS);
              return;
          } else if (InternalStateHandler.hasPending()
                  || AbstractFloatingView.getTopOpenView(launcher) != null) {
              // TODO: Move these checks to the top and call this method after invalidate handler.
              return;
          }
          incrementShelfBounceCount(launcher);
  
          new DiscoveryBounce(launcher, (1 - OVERVIEW.getVerticalProgress(launcher)))
                  .show(PREDICTION);
      }
  
      /**
       * A wrapper around {@link AllAppsTransitionController} allowing a fixed shift in the value.
       */
      public static class VerticalProgressWrapper {
  
          private final float mDelta;
          private final AllAppsTransitionController mController;
  
          private VerticalProgressWrapper(AllAppsTransitionController controller, float delta) {
              mController = controller;
              mDelta = delta;
          }
  
          public float getProgress() {
              return mController.getProgress() + mDelta;
          }
  
          public void setProgress(float progress) {
              mController.setProgress(progress - mDelta);
          }
      }
  
      private static boolean shouldShowForWorkProfile(Launcher launcher) {
          return !launcher.getSharedPrefs().getBoolean(
                  PersonalWorkSlidingTabStrip.KEY_SHOWED_PEEK_WORK_TAB, false)
                  && UserManagerCompat.getInstance(launcher).hasWorkProfile();
      }
  
      private static void incrementShelfBounceCount(Launcher launcher) {
          SharedPreferences sharedPrefs = launcher.getSharedPrefs();
          int count = sharedPrefs.getInt(SHELF_BOUNCE_COUNT, 0);
          if (count > BOUNCE_MAX_COUNT) {
              return;
          }
          sharedPrefs.edit().putInt(SHELF_BOUNCE_COUNT, count + 1).apply();
      }
  
      private static void incrementHomeBounceCount(Launcher launcher) {
          SharedPreferences sharedPrefs = launcher.getSharedPrefs();
          int count = sharedPrefs.getInt(HOME_BOUNCE_COUNT, 0);
          if (count > BOUNCE_MAX_COUNT) {
              return;
          }
          sharedPrefs.edit().putInt(HOME_BOUNCE_COUNT, count + 1).apply();
      }
  }
  