/* * 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 com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.animation.Interpolator; import com.android.launcher3.Alarm; import com.android.launcher3.BaseActivity; import com.android.launcher3.OnAlarmListener; import com.android.launcher3.Utilities; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; /** * Responds to quick scrub callbacks to page through and launch recent tasks. * * The behavior is to evenly divide the progress into sections, each of which scrolls one page. * The first and last section set an alarm to auto-advance backwards or forwards, respectively. */ public class QuickScrubController implements OnAlarmListener { public static final int QUICK_SCRUB_FROM_APP_START_DURATION = 240; public static final int QUICK_SCRUB_FROM_HOME_START_DURATION = 200; // We want the translation y to finish faster than the rest of the animation. public static final float QUICK_SCRUB_TRANSLATION_Y_FACTOR = 5f / 6; public static final Interpolator QUICK_SCRUB_START_INTERPOLATOR = FAST_OUT_SLOW_IN; /** * Snap to a new page when crossing these thresholds. The first and last auto-advance. */ private static final float[] QUICK_SCRUB_THRESHOLDS = new float[] { 0.05f, 0.20f, 0.35f, 0.50f, 0.65f, 0.80f, 0.95f }; private static final String TAG = "QuickScrubController"; private static final boolean ENABLE_AUTO_ADVANCE = true; private static final long AUTO_ADVANCE_DELAY = 500; private static final int QUICKSCRUB_SNAP_DURATION_PER_PAGE = 325; private static final int QUICKSCRUB_END_SNAP_DURATION_PER_PAGE = 60; private final Alarm mAutoAdvanceAlarm; private final RecentsView mRecentsView; private final BaseActivity mActivity; private boolean mInQuickScrub; private boolean mWaitingForTaskLaunch; private int mQuickScrubSection; private boolean mStartedFromHome; private boolean mFinishedTransitionToQuickScrub; private Runnable mOnFinishedTransitionToQuickScrubRunnable; private ActivityControlHelper mActivityControlHelper; public QuickScrubController(BaseActivity activity, RecentsView recentsView) { mActivity = activity; mRecentsView = recentsView; if (ENABLE_AUTO_ADVANCE) { mAutoAdvanceAlarm = new Alarm(); mAutoAdvanceAlarm.setOnAlarmListener(this); } } public void onQuickScrubStart(boolean startingFromHome, ActivityControlHelper controlHelper) { prepareQuickScrub(TAG); mInQuickScrub = true; mStartedFromHome = startingFromHome; mQuickScrubSection = 0; mFinishedTransitionToQuickScrub = false; mActivityControlHelper = controlHelper; snapToNextTaskIfAvailable(); mActivity.getUserEventDispatcher().resetActionDurationMillis(); } public void onQuickScrubEnd() { mInQuickScrub = false; if (ENABLE_AUTO_ADVANCE) { mAutoAdvanceAlarm.cancelAlarm(); } int page = mRecentsView.getNextPage(); Runnable launchTaskRunnable = () -> { TaskView taskView = mRecentsView.getPageAt(page); if (taskView != null) { mWaitingForTaskLaunch = true; taskView.launchTask(true, (result) -> { if (!result) { taskView.notifyTaskLaunchFailed(TAG); breakOutOfQuickScrub(); } else { mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(Touch.DRAGDROP, LauncherLogProto.Action.Direction.NONE, page, TaskUtils.getComponentKeyForTask(taskView.getTask().key)); } mWaitingForTaskLaunch = false; }, taskView.getHandler()); } else { breakOutOfQuickScrub(); } mActivityControlHelper = null; }; int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen()) * QUICKSCRUB_END_SNAP_DURATION_PER_PAGE; if (mRecentsView.getChildCount() > 0 && mRecentsView.snapToPage(page, snapDuration)) { // Settle on the page then launch it mRecentsView.setNextPageSwitchRunnable(launchTaskRunnable); } else { // No page move needed, just launch it if (mFinishedTransitionToQuickScrub) { launchTaskRunnable.run(); } else { mOnFinishedTransitionToQuickScrubRunnable = launchTaskRunnable; } } } public void cancelActiveQuickscrub() { if (!mInQuickScrub) { return; } Log.d(TAG, "Quickscrub was active, cancelling"); mInQuickScrub = false; mActivityControlHelper = null; mOnFinishedTransitionToQuickScrubRunnable = null; mRecentsView.setNextPageSwitchRunnable(null); } /** * Initializes the UI for quick scrub, returns true if success. */ public boolean prepareQuickScrub(String tag) { if (mWaitingForTaskLaunch || mInQuickScrub) { Log.d(tag, "Waiting for last scrub to finish, will skip this interaction"); return false; } mOnFinishedTransitionToQuickScrubRunnable = null; mRecentsView.setNextPageSwitchRunnable(null); return true; } public boolean isWaitingForTaskLaunch() { return mWaitingForTaskLaunch; } /** * Attempts to go to normal overview or back to home, so UI doesn't prevent user interaction. */ private void breakOutOfQuickScrub() { if (mRecentsView.getChildCount() == 0 || mActivityControlHelper == null || !mActivityControlHelper.switchToRecentsIfVisible(false)) { mActivity.onBackPressed(); } } public void onQuickScrubProgress(float progress) { int quickScrubSection = 0; for (float threshold : QUICK_SCRUB_THRESHOLDS) { if (progress < threshold) { break; } quickScrubSection++; } if (quickScrubSection != mQuickScrubSection) { boolean cameFromAutoAdvance = mQuickScrubSection == QUICK_SCRUB_THRESHOLDS.length || mQuickScrubSection == 0; int pageToGoTo = mRecentsView.getNextPage() + quickScrubSection - mQuickScrubSection; if (mFinishedTransitionToQuickScrub && !cameFromAutoAdvance) { goToPageWithHaptic(pageToGoTo); } if (ENABLE_AUTO_ADVANCE) { if (quickScrubSection == QUICK_SCRUB_THRESHOLDS.length || quickScrubSection == 0) { mAutoAdvanceAlarm.setAlarm(AUTO_ADVANCE_DELAY); } else { mAutoAdvanceAlarm.cancelAlarm(); } } mQuickScrubSection = quickScrubSection; } } public void onFinishedTransitionToQuickScrub() { mFinishedTransitionToQuickScrub = true; Runnable action = mOnFinishedTransitionToQuickScrubRunnable; // Clear the runnable before executing it, to prevent potential recursion. mOnFinishedTransitionToQuickScrubRunnable = null; if (action != null) { action.run(); } } public void snapToNextTaskIfAvailable() { if (mInQuickScrub && mRecentsView.getChildCount() > 0) { int duration = mStartedFromHome ? QUICK_SCRUB_FROM_HOME_START_DURATION : QUICK_SCRUB_FROM_APP_START_DURATION; int pageToGoTo = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1; goToPageWithHaptic(pageToGoTo, duration, true /* forceHaptic */, QUICK_SCRUB_START_INTERPOLATOR); } } private void goToPageWithHaptic(int pageToGoTo) { goToPageWithHaptic(pageToGoTo, -1 /* overrideDuration */, false /* forceHaptic */, null); } private void goToPageWithHaptic(int pageToGoTo, int overrideDuration, boolean forceHaptic, Interpolator interpolator) { pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1); boolean snappingToPage = pageToGoTo != mRecentsView.getNextPage(); if (snappingToPage) { int duration = overrideDuration > -1 ? overrideDuration : Math.abs(pageToGoTo - mRecentsView.getNextPage()) * QUICKSCRUB_SNAP_DURATION_PER_PAGE; mRecentsView.snapToPage(pageToGoTo, duration, interpolator); } if (snappingToPage || forceHaptic) { mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); } } @Override public void onAlarm(Alarm alarm) { int currPage = mRecentsView.getNextPage(); boolean recentsVisible = mActivityControlHelper != null && mActivityControlHelper.getVisibleRecentsView() != null; if (!recentsVisible) { Log.w(TAG, "Failed to auto advance; recents not visible"); return; } if (mQuickScrubSection == QUICK_SCRUB_THRESHOLDS.length && currPage < mRecentsView.getPageCount() - 1) { goToPageWithHaptic(currPage + 1); } else if (mQuickScrubSection == 0 && currPage > 0) { goToPageWithHaptic(currPage - 1); } if (ENABLE_AUTO_ADVANCE) { mAutoAdvanceAlarm.setAlarm(AUTO_ADVANCE_DELAY); } } }