blob: 4d8075a9b56c4be39660e5e8aa723c8c83126fc5 [file] [log] [blame]
/*
* Copyright (C) 2023 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.wm.shell.keyguard;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import static com.android.wm.shell.util.TransitionUtil.isClosingType;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.Log;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
import java.util.Map;
/**
* The handler for Keyguard enter/exit and occlude/unocclude animations.
*
* <p>This takes the highest priority.
*/
public class KeyguardTransitionHandler implements Transitions.TransitionHandler {
private static final String TAG = "KeyguardTransition";
private final Transitions mTransitions;
private final Handler mMainHandler;
private final ShellExecutor mMainExecutor;
private final Map<IBinder, IRemoteTransition> mStartedTransitions = new ArrayMap<>();
/**
* Local IRemoteTransition implementations registered by the keyguard service.
* @see KeyguardTransitions
*/
private IRemoteTransition mExitTransition = null;
private IRemoteTransition mOccludeTransition = null;
private IRemoteTransition mOccludeByDreamTransition = null;
private IRemoteTransition mUnoccludeTransition = null;
public KeyguardTransitionHandler(
@NonNull ShellInit shellInit,
@NonNull Transitions transitions,
@NonNull Handler mainHandler,
@NonNull ShellExecutor mainExecutor) {
mTransitions = transitions;
mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mTransitions.addHandler(this);
}
/**
* Interface for SystemUI implementations to set custom Keyguard exit/occlude handlers.
*/
@ExternalThread
public KeyguardTransitions asKeyguardTransitions() {
return new KeyguardTransitionsImpl();
}
public static boolean handles(TransitionInfo info) {
return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0
|| (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0
|| info.getType() == TRANSIT_KEYGUARD_OCCLUDE
|| info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE;
}
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
if (!handles(info)) {
return false;
}
boolean hasOpeningOcclude = false;
boolean hasOpeningDream = false;
boolean hasClosingApp = false;
// Check for occluding/dream/closing apps
for (int i = info.getChanges().size() - 1; i >= 0; i--) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (isOpeningType(change.getMode())) {
if (change.hasFlags(FLAG_OCCLUDES_KEYGUARD)) {
hasOpeningOcclude = true;
}
if (change.getTaskInfo() != null
&& change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_DREAM) {
hasOpeningDream = true;
}
} else if (isClosingType(change.getMode())) {
hasClosingApp = true;
}
}
// Choose a transition applicable for the changes and keyguard state.
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
return startAnimation(mExitTransition,
"going-away",
transition, info, startTransaction, finishTransaction, finishCallback);
}
if (hasOpeningOcclude || info.getType() == TRANSIT_KEYGUARD_OCCLUDE) {
if (hasOpeningDream) {
return startAnimation(mOccludeByDreamTransition,
"occlude-by-dream",
transition, info, startTransaction, finishTransaction, finishCallback);
} else {
return startAnimation(mOccludeTransition,
"occlude",
transition, info, startTransaction, finishTransaction, finishCallback);
}
} else if (hasClosingApp || info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE) {
return startAnimation(mUnoccludeTransition,
"unocclude",
transition, info, startTransaction, finishTransaction, finishCallback);
} else {
Log.wtf(TAG, "Failed to play: " + info);
return false;
}
}
private boolean startAnimation(IRemoteTransition remoteHandler, String description,
@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"start keyguard %s transition, info = %s", description, info);
try {
remoteHandler.startAnimation(transition, info, startTransaction,
new IRemoteTransitionFinishedCallback.Stub() {
@Override
public void onTransitionFinished(
WindowContainerTransaction wct, SurfaceControl.Transaction sct) {
mMainExecutor.execute(() -> {
finishCallback.onTransitionFinished(wct, null);
});
}
});
mStartedTransitions.put(transition, remoteHandler);
} catch (RemoteException e) {
Log.wtf(TAG, "RemoteException thrown from local IRemoteTransition", e);
return false;
}
startTransaction.clear();
return true;
}
@Override
public void mergeAnimation(@NonNull IBinder nextTransition, @NonNull TransitionInfo nextInfo,
@NonNull SurfaceControl.Transaction nextT, @NonNull IBinder currentTransition,
@NonNull TransitionFinishCallback nextFinishCallback) {
final IRemoteTransition playing = mStartedTransitions.get(currentTransition);
if (playing == null) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"unknown keyguard transition %s", currentTransition);
return;
}
if (nextInfo.getType() == TRANSIT_SLEEP) {
// An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep
// token is held. In cases where keyguard is showing, we are running the animation for
// the device sleeping/waking, so it's best to ignore this and keep playing anyway.
return;
} else {
finishAnimationImmediately(currentTransition);
}
}
@Override
public void onTransitionConsumed(IBinder transition, boolean aborted,
SurfaceControl.Transaction finishTransaction) {
finishAnimationImmediately(transition);
}
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
return null;
}
private void finishAnimationImmediately(IBinder transition) {
final IRemoteTransition playing = mStartedTransitions.get(transition);
if (playing != null) {
final IBinder fakeTransition = new Binder();
final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction();
final FakeFinishCallback fakeFinishCb = new FakeFinishCallback();
try {
playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
} catch (RemoteException e) {
// There is no good reason for this to happen because the player is a local object
// implementing an AIDL interface.
Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
}
}
}
private static class FakeFinishCallback extends IRemoteTransitionFinishedCallback.Stub {
@Override
public void onTransitionFinished(
WindowContainerTransaction wct, SurfaceControl.Transaction t) {
return;
}
}
@ExternalThread
private final class KeyguardTransitionsImpl implements KeyguardTransitions {
@Override
public void register(
IRemoteTransition exitTransition,
IRemoteTransition occludeTransition,
IRemoteTransition occludeByDreamTransition,
IRemoteTransition unoccludeTransition) {
mMainExecutor.execute(() -> {
mExitTransition = exitTransition;
mOccludeTransition = occludeTransition;
mOccludeByDreamTransition = occludeByDreamTransition;
mUnoccludeTransition = unoccludeTransition;
});
}
}
}