blob: da7a98f330299fdf977dfd267e9643e4daf25507 [file] [log] [blame]
/*
* Copyright (C) 2019 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 android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_CANCEL_RECENTS_ANIMATION;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_FINISH_RECENTS_ANIMATION;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_START_RECENTS_ANIMATION;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.ArraySet;
import android.view.RemoteAnimationTarget;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Set;
/**
* Wrapper around {@link com.android.systemui.shared.system.RecentsAnimationListener} which
* delegates callbacks to multiple listeners on the main thread
*/
public class RecentsAnimationCallbacks implements
com.android.systemui.shared.system.RecentsAnimationListener {
private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
private final SystemUiProxy mSystemUiProxy;
private final boolean mAllowMinimizeSplitScreen;
// TODO(141886704): Remove these references when they are no longer needed
private RecentsAnimationController mController;
private boolean mCancelled;
public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy,
boolean allowMinimizeSplitScreen) {
mSystemUiProxy = systemUiProxy;
mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
}
@UiThread
public void addListener(RecentsAnimationListener listener) {
Preconditions.assertUIThread();
mListeners.add(listener);
}
@UiThread
public void removeListener(RecentsAnimationListener listener) {
Preconditions.assertUIThread();
mListeners.remove(listener);
}
@UiThread
public void removeAllListeners() {
Preconditions.assertUIThread();
mListeners.clear();
}
public void notifyAnimationCanceled() {
mCancelled = true;
onAnimationCanceled(new HashMap<>());
}
// Called only in Q platform
@BinderThread
@Deprecated
public final void onAnimationStart(RecentsAnimationControllerCompat controller,
RemoteAnimationTarget[] appTargets, Rect homeContentInsets,
Rect minimizedHomeBounds, Bundle extras) {
onAnimationStart(controller, appTargets, new RemoteAnimationTarget[0],
homeContentInsets, minimizedHomeBounds, extras);
}
// Called only in R+ platform
@BinderThread
public final void onAnimationStart(RecentsAnimationControllerCompat animationController,
RemoteAnimationTarget[] appTargets,
RemoteAnimationTarget[] wallpaperTargets,
Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras) {
long appCount = Arrays.stream(appTargets)
.filter(app -> app.mode == MODE_CLOSING)
.count();
if (appCount == 0) {
// Edge case, if there are no closing app targets, then Launcher has nothing to handle
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "RecentsAnimationCallbacks.onAnimationStart (canceled)",
/* extras= */ 0,
/* gestureEvent= */ ON_START_RECENTS_ANIMATION);
notifyAnimationCanceled();
animationController.finish(false /* toHome */, false /* sendUserLeaveHint */,
null /* finishCb */);
return;
}
mController = new RecentsAnimationController(animationController,
mAllowMinimizeSplitScreen, this::onAnimationFinished);
if (mCancelled) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
mController::finishAnimationToApp);
} else {
RemoteAnimationTarget[] nonAppTargets;
if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy(appTargets);
} else {
final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
final ArrayList<RemoteAnimationTarget> nonApps = new ArrayList<>();
classifyTargets(appTargets, apps, nonApps);
appTargets = apps.toArray(new RemoteAnimationTarget[apps.size()]);
nonAppTargets = nonApps.toArray(new RemoteAnimationTarget[nonApps.size()]);
}
if (nonAppTargets == null) {
nonAppTargets = new RemoteAnimationTarget[0];
}
final RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
wallpaperTargets, nonAppTargets, homeContentInsets, minimizedHomeBounds,
extras);
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "RecentsAnimationCallbacks.onAnimationStart",
/* extras= */ targets.apps.length,
/* gestureEvent= */ ON_START_RECENTS_ANIMATION);
for (RecentsAnimationListener listener : getListeners()) {
listener.onRecentsAnimationStart(mController, targets);
}
});
}
}
@BinderThread
@Override
public final void onAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "RecentsAnimationCallbacks.onAnimationCanceled",
/* gestureEvent= */ ON_CANCEL_RECENTS_ANIMATION);
for (RecentsAnimationListener listener : getListeners()) {
listener.onRecentsAnimationCanceled(thumbnailDatas);
}
});
}
@BinderThread
@Override
public void onTasksAppeared(RemoteAnimationTarget[] apps) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
ActiveGestureLog.INSTANCE.addLog("RecentsAnimationCallbacks.onTasksAppeared",
ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED);
for (RecentsAnimationListener listener : getListeners()) {
listener.onTasksAppeared(apps);
}
});
}
@BinderThread
@Override
public boolean onSwitchToScreenshot(Runnable onFinished) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
for (RecentsAnimationListener listener : getListeners()) {
if (listener.onSwitchToScreenshot(onFinished)) return;
}
onFinished.run();
});
return true;
}
private final void onAnimationFinished(RecentsAnimationController controller) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "RecentsAnimationCallbacks.onAnimationFinished",
ON_FINISH_RECENTS_ANIMATION);
for (RecentsAnimationListener listener : getListeners()) {
listener.onRecentsAnimationFinished(controller);
}
});
}
private RecentsAnimationListener[] getListeners() {
return mListeners.toArray(new RecentsAnimationListener[mListeners.size()]);
}
private void classifyTargets(RemoteAnimationTarget[] appTargets,
ArrayList<RemoteAnimationTarget> apps, ArrayList<RemoteAnimationTarget> nonApps) {
for (int i = 0; i < appTargets.length; i++) {
RemoteAnimationTarget target = appTargets[i];
if (target.windowType == TYPE_DOCK_DIVIDER) {
nonApps.add(target);
} else {
apps.add(target);
}
}
}
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "RecentsAnimationCallbacks:");
pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
pw.println(prefix + "\tmCancelled=" + mCancelled);
}
/**
* Listener for the recents animation callbacks.
*/
public interface RecentsAnimationListener {
default void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {}
/**
* Callback from the system when the recents animation is canceled. {@param thumbnailData}
* is passed back for rendering screenshot to replace live tile.
*/
default void onRecentsAnimationCanceled(
@NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {}
/**
* Callback made whenever the recents animation is finished.
*/
default void onRecentsAnimationFinished(@NonNull RecentsAnimationController controller) {}
/**
* Callback made when a task started from the recents is ready for an app transition.
*/
default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget) {}
/**
* @return whether this will call onFinished or not (onFinished should only be called once).
*/
default boolean onSwitchToScreenshot(Runnable onFinished) {
return false;
}
}
}