blob: 7a79c6f4df0c54fddd7aa8692cf37d59760b0617 [file] [log] [blame]
/*
* 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);
}
}
}