| /* |
| * 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); |
| } |
| } |
| } |
| } |