blob: 653d73020c4fe894c21ad7e8583c1fac28d8d478 [file] [log] [blame]
/*
* Copyright (C) 2020 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.systemui.shared.system;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.view.IRecentsAnimationController;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.PictureInPictureSurfaceTransaction;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.concurrent.Executor;
/**
* Wrapper to expose RemoteTransition (shell transitions) to Launcher.
*
* @see IRemoteTransition
* @see TransitionFilter
*/
@DataClass
public class RemoteTransitionCompat implements Parcelable {
private static final String TAG = "RemoteTransitionCompat";
@NonNull final IRemoteTransition mTransition;
@Nullable TransitionFilter mFilter = null;
RemoteTransitionCompat(IRemoteTransition transition) {
mTransition = transition;
}
public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner,
@NonNull Executor executor) {
mTransition = new IRemoteTransition.Stub() {
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishedCallback) {
final Runnable finishAdapter = () -> {
try {
finishedCallback.onTransitionFinished(null /* wct */);
} catch (RemoteException e) {
Log.e(TAG, "Failed to call transition finished callback", e);
}
};
executor.execute(() -> runner.startAnimation(transition, info, t, finishAdapter));
}
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishedCallback) {
final Runnable finishAdapter = () -> {
try {
finishedCallback.onTransitionFinished(null /* wct */);
} catch (RemoteException e) {
Log.e(TAG, "Failed to call transition finished callback", e);
}
};
executor.execute(() -> runner.mergeAnimation(transition, info, t, mergeTarget,
finishAdapter));
}
};
}
/** Constructor specifically for recents animation */
public RemoteTransitionCompat(RecentsAnimationListener recents,
RecentsAnimationControllerCompat controller) {
mTransition = new IRemoteTransition.Stub() {
final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
IBinder mToken = null;
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishedCallback) {
final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
final RemoteAnimationTargetCompat[] apps =
RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */, t, leashMap);
final RemoteAnimationTargetCompat[] wallpapers =
RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */, t, leashMap);
// TODO(b/177438007): Move this set-up logic into launcher's animation impl.
mToken = transition;
// This transition is for opening recents, so recents is on-top. We want to draw
// the current going-away task on top of recents, though, so move it to front
WindowContainerToken pausingTask = null;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.getMode() == TRANSIT_CLOSE || change.getMode() == TRANSIT_TO_BACK) {
t.setLayer(leashMap.get(change.getLeash()),
info.getChanges().size() * 3 - i);
if (change.getTaskInfo() != null) {
pausingTask = change.getTaskInfo().token;
}
}
}
// Also make all the wallpapers opaque since we want the visible from the start
for (int i = wallpapers.length - 1; i >= 0; --i) {
t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1);
}
t.apply();
mRecentsSession.setup(controller, info, finishedCallback, pausingTask,
leashMap);
recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0),
new Rect());
}
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishedCallback) {
if (!mergeTarget.equals(mToken)) return;
if (!mRecentsSession.merge(info, t, recents)) return;
try {
finishedCallback.onTransitionFinished(null /* wct */);
} catch (RemoteException e) {
Log.e(TAG, "Error merging transition.", e);
}
}
};
}
/** Adds a filter check that restricts this remote transition to home open transitions. */
public void addHomeOpenCheck() {
if (mFilter == null) {
mFilter = new TransitionFilter();
}
mFilter.mRequirements =
new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
}
/**
* Wrapper to hook up parts of recents animation to shell transition.
* TODO(b/177438007): Remove this once Launcher handles shell transitions directly.
*/
@VisibleForTesting
static class RecentsControllerWrap extends RecentsAnimationControllerCompat {
private RecentsAnimationControllerCompat mWrapped = null;
private IRemoteTransitionFinishedCallback mFinishCB = null;
private WindowContainerToken mPausingTask = null;
private TransitionInfo mInfo = null;
private SurfaceControl mOpeningLeash = null;
private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
IRemoteTransitionFinishedCallback finishCB, WindowContainerToken pausingTask,
ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
if (mInfo != null) {
throw new IllegalStateException("Trying to run a new recents animation while"
+ " recents is already active.");
}
mWrapped = wrapped;
mInfo = info;
mFinishCB = finishCB;
mPausingTask = pausingTask;
mLeashMap = leashMap;
}
@SuppressLint("NewApi")
boolean merge(TransitionInfo info, SurfaceControl.Transaction t,
RecentsAnimationListener recents) {
TransitionInfo.Change openingTask = null;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
if (change.getTaskInfo() != null) {
if (openingTask != null) {
Log.w(TAG, " Expecting to merge a task-open, but got >1 opening "
+ "tasks");
}
openingTask = change;
}
}
}
if (openingTask == null) return false;
mOpeningLeash = openingTask.getLeash();
if (openingTask.getContainer().equals(mPausingTask)) {
// In this case, we are "returning" to the already running app, so just consume
// the merge and do nothing.
return true;
}
// We are receiving a new opening task, so convert to onTaskAppeared.
final int layer = mInfo.getChanges().size() * 3;
final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
openingTask, layer, mInfo, t);
mLeashMap.put(mOpeningLeash, target.leash.mSurfaceControl);
t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash());
t.setLayer(target.leash.mSurfaceControl, layer);
t.hide(target.leash.mSurfaceControl);
t.apply();
recents.onTaskAppeared(target);
return true;
}
@Override public ThumbnailData screenshotTask(int taskId) {
return mWrapped != null ? mWrapped.screenshotTask(taskId) : null;
}
@Override public void setInputConsumerEnabled(boolean enabled) {
if (mWrapped != null) mWrapped.setInputConsumerEnabled(enabled);
}
@Override public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
if (mWrapped != null) mWrapped.setAnimationTargetsBehindSystemBars(behindSystemBars);
}
@Override public void hideCurrentInputMethod() {
mWrapped.hideCurrentInputMethod();
}
@Override public void setFinishTaskTransaction(int taskId,
PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
if (mWrapped != null) {
mWrapped.setFinishTaskTransaction(taskId, finishTransaction, overlay);
}
}
@Override
@SuppressLint("NewApi")
public void finish(boolean toHome, boolean sendUserLeaveHint) {
if (mFinishCB == null) {
Log.e(TAG, "Duplicate call to finish", new RuntimeException());
return;
}
if (mWrapped != null) mWrapped.finish(toHome, sendUserLeaveHint);
try {
if (!toHome && mPausingTask != null && mOpeningLeash == null) {
// The gesture went back to opening the app rather than continuing with
// recents, so end the transition by moving the app back to the top.
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mPausingTask, true /* onTop */);
mFinishCB.onTransitionFinished(wct);
} else {
if (mOpeningLeash != null) {
// TODO: the launcher animation should handle this
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
t.show(mOpeningLeash);
t.setAlpha(mOpeningLeash, 1.f);
t.apply();
}
mFinishCB.onTransitionFinished(null /* wct */);
}
} catch (RemoteException e) {
Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e);
}
// Release surface references now. This is apparently to free GPU
// memory while doing quick operations (eg. during CTS).
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
for (int i = 0; i < mLeashMap.size(); ++i) {
if (mLeashMap.keyAt(i) == mLeashMap.valueAt(i)) continue;
t.remove(mLeashMap.valueAt(i));
}
t.apply();
for (int i = 0; i < mInfo.getChanges().size(); ++i) {
mInfo.getChanges().get(i).getLeash().release();
}
// Reset all members.
mWrapped = null;
mFinishCB = null;
mPausingTask = null;
mInfo = null;
mOpeningLeash = null;
mLeashMap = null;
}
@Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
if (mWrapped != null) mWrapped.setDeferCancelUntilNextTransition(defer, screenshot);
}
@Override public void cleanupScreenshot() {
if (mWrapped != null) mWrapped.cleanupScreenshot();
}
@Override public void setWillFinishToHome(boolean willFinishToHome) {
if (mWrapped != null) mWrapped.setWillFinishToHome(willFinishToHome);
}
/**
* @see IRecentsAnimationController#removeTask
*/
@Override public boolean removeTask(int taskId) {
return mWrapped != null ? mWrapped.removeTask(taskId) : false;
}
}
// Code below generated by codegen v1.0.21.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
@DataClass.Generated.Member
public @NonNull IRemoteTransition getTransition() {
return mTransition;
}
@DataClass.Generated.Member
public @Nullable TransitionFilter getFilter() {
return mFilter;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
if (mFilter != null) flg |= 0x2;
dest.writeByte(flg);
dest.writeStrongInterface(mTransition);
if (mFilter != null) dest.writeTypedObject(mFilter, flags);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
protected RemoteTransitionCompat(@NonNull android.os.Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
IRemoteTransition transition = IRemoteTransition.Stub.asInterface(in.readStrongBinder());
TransitionFilter filter = (flg & 0x2) == 0 ? null : (TransitionFilter) in.readTypedObject(TransitionFilter.CREATOR);
this.mTransition = transition;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mTransition);
this.mFilter = filter;
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<RemoteTransitionCompat> CREATOR
= new Parcelable.Creator<RemoteTransitionCompat>() {
@Override
public RemoteTransitionCompat[] newArray(int size) {
return new RemoteTransitionCompat[size];
}
@Override
public RemoteTransitionCompat createFromParcel(@NonNull android.os.Parcel in) {
return new RemoteTransitionCompat(in);
}
};
/**
* A builder for {@link RemoteTransitionCompat}
*/
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
public static class Builder {
private @NonNull IRemoteTransition mTransition;
private @Nullable TransitionFilter mFilter;
private long mBuilderFieldsSet = 0L;
public Builder(
@NonNull IRemoteTransition transition) {
mTransition = transition;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mTransition);
}
@DataClass.Generated.Member
public @NonNull Builder setTransition(@NonNull IRemoteTransition value) {
checkNotUsed();
mBuilderFieldsSet |= 0x1;
mTransition = value;
return this;
}
@DataClass.Generated.Member
public @NonNull Builder setFilter(@NonNull TransitionFilter value) {
checkNotUsed();
mBuilderFieldsSet |= 0x2;
mFilter = value;
return this;
}
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull RemoteTransitionCompat build() {
checkNotUsed();
mBuilderFieldsSet |= 0x4; // Mark builder used
if ((mBuilderFieldsSet & 0x2) == 0) {
mFilter = null;
}
RemoteTransitionCompat o = new RemoteTransitionCompat(mTransition);
o.mFilter = this.mFilter;
return o;
}
private void checkNotUsed() {
if ((mBuilderFieldsSet & 0x4) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
}
}
@DataClass.Generated(
time = 1606862689344L,
codegenVersion = "1.0.21",
sourceFile = "frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java",
inputSignatures = "final @android.annotation.NonNull com.android.systemui.shared.system.IRemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic void addHomeOpenCheck()\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}