blob: 17baa3ad91f4f8f1aa3157bb58c0296616f5980c [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.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
import android.annotation.TargetApi;
import android.content.Intent;
import android.graphics.PointF;
import android.os.Build;
import android.os.SystemClock;
import android.os.Trace;
import androidx.annotation.BinderThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Helper class to handle various atomic commands for switching between Overview.
*/
@TargetApi(Build.VERSION_CODES.P)
public class OverviewCommandHelper {
public static final int TYPE_SHOW = 1;
public static final int TYPE_SHOW_NEXT_FOCUS = 2;
public static final int TYPE_HIDE = 3;
public static final int TYPE_TOGGLE = 4;
public static final int TYPE_HOME = 5;
private static final String TRANSITION_NAME = "Transition:toOverview";
private final TouchInteractionService mService;
private final OverviewComponentObserver mOverviewComponentObserver;
private final TaskAnimationManager mTaskAnimationManager;
private final ArrayList<CommandInfo> mPendingCommands = new ArrayList<>();
public OverviewCommandHelper(TouchInteractionService service,
OverviewComponentObserver observer,
TaskAnimationManager taskAnimationManager) {
mService = service;
mOverviewComponentObserver = observer;
mTaskAnimationManager = taskAnimationManager;
}
/**
* Called when the command finishes execution.
*/
private void scheduleNextTask(CommandInfo command) {
if (!mPendingCommands.isEmpty() && mPendingCommands.get(0) == command) {
mPendingCommands.remove(0);
executeNext();
}
}
/**
* Executes the next command from the queue. If the command finishes immediately (returns true),
* it continues to execute the next command, until the queue is empty of a command defer's its
* completion (returns false).
*/
@UiThread
private void executeNext() {
if (mPendingCommands.isEmpty()) {
return;
}
CommandInfo cmd = mPendingCommands.get(0);
if (executeCommand(cmd)) {
scheduleNextTask(cmd);
}
}
@UiThread
private void addCommand(CommandInfo cmd) {
boolean wasEmpty = mPendingCommands.isEmpty();
mPendingCommands.add(cmd);
if (wasEmpty) {
executeNext();
}
}
/**
* Adds a command to be executed next, after all pending tasks are completed
*/
@BinderThread
public void addCommand(int type) {
CommandInfo cmd = new CommandInfo(type);
MAIN_EXECUTOR.execute(() -> addCommand(cmd));
}
@UiThread
public void clearPendingCommands() {
mPendingCommands.clear();
}
@Nullable
private TaskView getNextTask(RecentsView view) {
final TaskView runningTaskView = view.getRunningTaskView();
if (runningTaskView == null) {
return view.getTaskViewAt(0);
} else {
final TaskView nextTask = view.getNextTaskView();
return nextTask != null ? nextTask : runningTaskView;
}
}
private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) {
RunnableList callbackList = null;
if (taskView != null) {
taskView.setEndQuickswitchCuj(true);
callbackList = taskView.launchTaskAnimated();
}
if (callbackList != null) {
callbackList.add(() -> scheduleNextTask(cmd));
return false;
} else {
recents.startHome();
return true;
}
}
/**
* Executes the task and returns true if next task can be executed. If false, then the next
* task is deferred until {@link #scheduleNextTask} is called
*/
private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
BaseActivityInterface<?, T> activityInterface =
mOverviewComponentObserver.getActivityInterface();
RecentsView recents = activityInterface.getVisibleRecentsView();
if (recents == null) {
if (cmd.type == TYPE_HIDE) {
// already hidden
return true;
}
if (cmd.type == TYPE_HOME) {
mService.startActivity(mOverviewComponentObserver.getHomeIntent());
LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
return true;
}
} else {
switch (cmd.type) {
case TYPE_SHOW:
// already visible
return true;
case TYPE_HIDE: {
int currentPage = recents.getNextPage();
TaskView tv = (currentPage >= 0 && currentPage < recents.getTaskViewCount())
? (TaskView) recents.getPageAt(currentPage)
: null;
return launchTask(recents, tv, cmd);
}
case TYPE_TOGGLE:
return launchTask(recents, getNextTask(recents), cmd);
case TYPE_HOME:
recents.startHome();
LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
return true;
}
}
if (activityInterface.switchToRecentsIfVisible(() -> scheduleNextTask(cmd))) {
// If successfully switched, wait until animation finishes
return false;
}
final T activity = activityInterface.getCreatedActivity();
if (activity != null) {
InteractionJankMonitorWrapper.begin(
activity.getRootView(),
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
}
GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE);
gestureState.setHandlingAtomicEvent(true);
AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory()
.newHandler(gestureState, cmd.createTime);
interactionHandler.setGestureEndCallback(
() -> onTransitionComplete(cmd, interactionHandler));
interactionHandler.initWhenReady();
RecentsAnimationListener recentAnimListener = new RecentsAnimationListener() {
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
interactionHandler.onGestureEnded(0, new PointF(), new PointF());
cmd.removeListener(this);
}
@Override
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
interactionHandler.onGestureCancelled();
cmd.removeListener(this);
RecentsView createdRecents =
activityInterface.getCreatedActivity().getOverviewPanel();
if (createdRecents != null) {
createdRecents.onRecentsAnimationComplete();
}
}
};
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
cmd.mActiveCallbacks.addListener(interactionHandler);
mTaskAnimationManager.notifyRecentsAnimationState(interactionHandler);
interactionHandler.onGestureStarted(true /*isLikelyToStartNewTask*/);
cmd.mActiveCallbacks.addListener(recentAnimListener);
mTaskAnimationManager.notifyRecentsAnimationState(recentAnimListener);
} else {
Intent intent = new Intent(interactionHandler.getLaunchIntent());
intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, gestureState.getGestureId());
cmd.mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(
gestureState, intent, interactionHandler);
interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/);
cmd.mActiveCallbacks.addListener(recentAnimListener);
}
Trace.beginAsyncSection(TRANSITION_NAME, 0);
return false;
}
private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) {
cmd.removeListener(handler);
Trace.endAsyncSection(TRANSITION_NAME, 0);
if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
RecentsView rv =
mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
if (rv != null) {
// Ensure that recents view has focus so that it receives the followup key inputs
TaskView taskView = rv.getNextTaskView();
if (taskView == null) {
taskView = rv.getTaskViewAt(0);
if (taskView != null) {
taskView.requestFocus();
} else {
rv.requestFocus();
}
} else {
taskView.requestFocus();
}
}
}
scheduleNextTask(cmd);
}
public void dump(PrintWriter pw) {
pw.println("OverviewCommandHelper:");
pw.println(" mPendingCommands=" + mPendingCommands.size());
if (!mPendingCommands.isEmpty()) {
pw.println(" pendingCommandType=" + mPendingCommands.get(0).type);
}
}
private static class CommandInfo {
public final long createTime = SystemClock.elapsedRealtime();
public final int type;
RecentsAnimationCallbacks mActiveCallbacks;
CommandInfo(int type) {
this.type = type;
}
void removeListener(RecentsAnimationListener listener) {
if (mActiveCallbacks != null) {
mActiveCallbacks.removeListener(listener);
}
}
}
}