blob: e28262dfbe2f3dc67cfe479d05f534f1549bc23a [file] [log] [blame]
/*
* Copyright (C) 2016 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.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
import static android.content.pm.ActivityInfo.reverseOrientation;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
import static com.android.server.wm.AppTransition.isActivityTransitOld;
import static com.android.server.wm.AppTransition.isTaskFragmentTransitOld;
import static com.android.server.wm.AppTransition.isTaskTransitOld;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowContainerProto.CONFIGURATION_CONTAINER;
import static com.android.server.wm.WindowContainerProto.IDENTIFIER;
import static com.android.server.wm.WindowContainerProto.ORIENTATION;
import static com.android.server.wm.WindowContainerProto.SURFACE_ANIMATOR;
import static com.android.server.wm.WindowContainerProto.SURFACE_CONTROL;
import static com.android.server.wm.WindowContainerProto.VISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Pools;
import android.util.RotationUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Builder;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
import android.view.TaskTransitionSpec;
import android.view.WindowManager;
import android.view.WindowManager.TransitionOldType;
import android.view.animation.Animation;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.wm.SurfaceAnimator.Animatable;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Defines common functionality for classes that can hold windows directly or through their
* children in a hierarchy form.
* The test class is {@link WindowContainerTests} which must be kept up-to-date and ran anytime
* changes are made to this class.
*/
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
InsetsControlTarget {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;
static final int POSITION_TOP = Integer.MAX_VALUE;
static final int POSITION_BOTTOM = Integer.MIN_VALUE;
/**
* The parent of this window container.
* For removing or setting new parent {@link #setParent} should be used, because it also
* performs configuration updates based on new parent's settings.
*/
private WindowContainer<WindowContainer> mParent = null;
// Set to true when we are performing a reparenting operation so we only send one
// onParentChanged() notification.
boolean mReparenting;
/**
* Map of the source ID to the {@link InsetsSource} for all children of the current
* {@link WindowContainer}.
*
* Note that these sources are not part of the {@link InsetsStateController} and live here.
* These are supposed to provide insets only to the subtree of this {@link WindowContainer}.
*/
@Nullable
SparseArray<InsetsSource> mLocalInsetsSources = null;
@Nullable
protected InsetsSourceProvider mControllableInsetProvider;
/**
* The {@link InsetsSourceProvider}s provided by this window container.
*/
protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null;
@Nullable
private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
// List of children for this window container. List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
protected final WindowList<E> mChildren = new WindowList<E>();
// The specified orientation for this window container.
// Shouldn't be accessed directly since subclasses can override getOverrideOrientation.
@ScreenOrientation
private int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
/**
* The window container which decides its orientation since the last time
* {@link #getOrientation(int) was called.
*/
protected WindowContainer mLastOrientationSource;
private final Pools.SynchronizedPool<ForAllWindowsConsumerWrapper> mConsumerWrapperPool =
new Pools.SynchronizedPool<>(3);
// The display this window container is on.
protected DisplayContent mDisplayContent;
protected SurfaceControl mSurfaceControl;
private int mLastLayer = 0;
private SurfaceControl mLastRelativeToLayer = null;
// TODO(b/132320879): Remove this from WindowContainers except DisplayContent.
private final Transaction mPendingTransaction;
/**
* Windows that clients are waiting to have drawn.
*/
final ArrayList<WindowState> mWaitingForDrawn = new ArrayList<>();
/**
* Applied as part of the animation pass in "prepareSurfaces".
*/
protected final SurfaceAnimator mSurfaceAnimator;
/** The parent leash added for animation. */
@Nullable
private SurfaceControl mAnimationLeash;
final SurfaceFreezer mSurfaceFreezer;
protected final WindowManagerService mWmService;
final TransitionController mTransitionController;
/**
* Sources which triggered a surface animation on this container. An animation target can be
* promoted to higher level, for example, from a set of {@link ActivityRecord}s to
* {@link Task}. In this case, {@link ActivityRecord}s are set on this variable while
* the animation is running, and reset after finishing it.
*/
private final ArraySet<WindowContainer> mSurfaceAnimationSources = new ArraySet<>();
private final Point mTmpPos = new Point();
protected final Point mLastSurfacePosition = new Point();
protected @Surface.Rotation int mLastDeltaRotation = Surface.ROTATION_0;
/** Total number of elements in this subtree, including our own hierarchy element. */
private int mTreeWeight = 1;
/**
* Indicates whether we are animating and have committed the transaction to reparent our
* surface to the animation leash
*/
private boolean mCommittedReparentToAnimationLeash;
private int mSyncTransactionCommitCallbackDepth = 0;
/** Interface for {@link #isAnimating} to check which cases for the container is animating. */
public interface AnimationFlags {
/**
* A bit flag indicates that {@link #isAnimating} should also return {@code true}
* even though the container is not yet animating, but the window container or its
* relatives as specified by PARENTS or CHILDREN are part of an {@link AppTransition}
* that is pending so an animation starts soon.
*/
int TRANSITION = 1;
/**
* A bit flag indicates that {@link #isAnimating} should also check if one of the
* ancestors of the container are animating in addition to the container itself.
*/
int PARENTS = 2;
/**
* A bit flag indicates that {@link #isAnimating} should also check if one of the
* descendants of the container are animating in addition to the container itself.
*/
int CHILDREN = 4;
}
/**
* True if this an AppWindowToken and the activity which created this was launched with
* ActivityOptions.setLaunchTaskBehind.
*
* TODO(b/142617871): We run a special animation when the activity was launched with that
* flag, but it's not necessary anymore. Keep the window invisible until the task is explicitly
* selected to suppress an animation, and remove this flag.
*/
boolean mLaunchTaskBehind;
/**
* If we are running an animation, this determines the transition type.
*/
@TransitionOldType int mTransit;
/**
* If we are running an animation, this determines the flags during this animation. Must be a
* bitwise combination of AppTransition.TRANSIT_FLAG_* constants.
*/
int mTransitFlags;
/** Whether this container should be boosted at the top of all its siblings. */
@VisibleForTesting boolean mNeedsZBoost;
/** Layer used to constrain the animation to a container's stack bounds. */
SurfaceControl mAnimationBoundsLayer;
/** Whether this container needs to create mAnimationBoundsLayer for cropping animations. */
boolean mNeedsAnimationBoundsLayer;
/**
* This gets used during some open/close transitions as well as during a change transition
* where it represents the starting-state snapshot.
*/
WindowContainerThumbnail mThumbnail;
final Point mTmpPoint = new Point();
protected final Rect mTmpRect = new Rect();
final Rect mTmpPrevBounds = new Rect();
private MagnificationSpec mLastMagnificationSpec;
private boolean mIsFocusable = true;
/**
* This indicates whether this window is visible by policy. This can precede physical
* visibility ({@link #isVisible} - whether it has a surface showing on the screen) in
* cases where an animation is on-going.
*/
protected boolean mVisibleRequested;
/**
* Used as a unique, cross-process identifier for this Container. It also serves a minimal
* interface to other processes.
*/
RemoteToken mRemoteToken = null;
/** This isn't participating in a sync. */
public static final int SYNC_STATE_NONE = 0;
/** This is currently waiting for itself to finish drawing. */
public static final int SYNC_STATE_WAITING_FOR_DRAW = 1;
/** This container is ready, but it might still have unfinished children. */
public static final int SYNC_STATE_READY = 2;
@IntDef(prefix = { "SYNC_STATE_" }, value = {
SYNC_STATE_NONE,
SYNC_STATE_WAITING_FOR_DRAW,
SYNC_STATE_READY,
})
@interface SyncState {}
/**
* If non-null, references the sync-group directly waiting on this container. Otherwise, this
* container is only being waited-on by its parents (if in a sync-group). This has implications
* on how this container is handled during parent changes.
*/
BLASTSyncEngine.SyncGroup mSyncGroup = null;
final SurfaceControl.Transaction mSyncTransaction;
@SyncState int mSyncState = SYNC_STATE_NONE;
int mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
private final List<WindowContainerListener> mListeners = new ArrayList<>();
protected TrustedOverlayHost mOverlayHost;
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mTransitionController = mWmService.mAtmService.getTransitionController();
mPendingTransaction = wms.mTransactionFactory.get();
mSyncTransaction = wms.mTransactionFactory.get();
mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
mSurfaceFreezer = new SurfaceFreezer(this, wms);
}
/**
* Updates the {@link WindowState#mAboveInsetsState} and
* {@link WindowState#mMergedLocalInsetsSources} by visiting the entire hierarchy.
*
* {@link WindowState#mAboveInsetsState} is updated by visiting all the windows in z-order
* top-to-bottom manner and considering the {@link WindowContainer#mInsetsSourceProviders}
* provided by the {@link WindowState}s at the top.
* {@link WindowState#updateAboveInsetsState(InsetsState, SparseArray, ArraySet)} visits the
* IME container in the correct order to make sure the IME insets are passed correctly to the
* {@link WindowState}s below it.
*
* {@link WindowState#mMergedLocalInsetsSources} is updated by considering
* {@link WindowContainer#mLocalInsetsSources} provided by all the parents of the window.
*
* Examples: Please take a look at
* {@link WindowContainerTests#testAddLocalInsetsSourceProvider()}
* {@link WindowContainerTests#testRemoveLocalInsetsSourceProvider()}.
*
* @param aboveInsetsState The InsetsState of all the Windows above the current
* container.
* @param localInsetsSourcesFromParent The local InsetsSourceProviders provided by all
* the parents in the hierarchy of the current
* container.
* @param insetsChangedWindows The windows which the insets changed have changed for.
*/
void updateAboveInsetsState(InsetsState aboveInsetsState,
SparseArray<InsetsSource> localInsetsSourcesFromParent,
ArraySet<WindowState> insetsChangedWindows) {
final SparseArray<InsetsSource> mergedLocalInsetsSources =
createMergedSparseArray(localInsetsSourcesFromParent, mLocalInsetsSources);
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).updateAboveInsetsState(aboveInsetsState, mergedLocalInsetsSources,
insetsChangedWindows);
}
}
static <T> SparseArray<T> createMergedSparseArray(SparseArray<T> sa1, SparseArray<T> sa2) {
final int size1 = sa1 != null ? sa1.size() : 0;
final int size2 = sa2 != null ? sa2.size() : 0;
final SparseArray<T> mergedArray = new SparseArray<>(size1 + size2);
if (size1 > 0) {
for (int i = 0; i < size1; i++) {
mergedArray.append(sa1.keyAt(i), sa1.valueAt(i));
}
}
if (size2 > 0) {
for (int i = 0; i < size2; i++) {
mergedArray.put(sa2.keyAt(i), sa2.valueAt(i));
}
}
return mergedArray;
}
/**
* Adds an {@link InsetsFrameProvider} which describes what insets should be provided to
* this {@link WindowContainer} and its children.
*
* @param provider describes the insets type and the frame.
* @param owner owns the insets source which only exists when the owner is alive.
*/
void addLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) {
if (provider == null || owner == null) {
throw new IllegalArgumentException("Insets provider or owner not specified.");
}
if (mDisplayContent == null) {
// This is possible this container is detached when WM shell is responding to a previous
// request. WM shell will be updated when this container is attached again and the
// insets need to be updated.
Slog.w(TAG, "Can't add insets frame provider when detached. " + this);
return;
}
if (mInsetsOwnerDeathRecipientMap == null) {
mInsetsOwnerDeathRecipientMap = new ArrayMap<>();
}
DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner);
if (deathRecipient == null) {
deathRecipient = new DeathRecipient(owner);
try {
owner.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to add source for " + provider + " since the owner has died.");
return;
}
mInsetsOwnerDeathRecipientMap.put(owner, deathRecipient);
}
final int id = provider.getId();
deathRecipient.addSourceId(id);
if (mLocalInsetsSources == null) {
mLocalInsetsSources = new SparseArray<>();
}
if (mLocalInsetsSources.get(id) != null) {
if (DEBUG) {
Slog.d(TAG, "The local insets source for this " + provider
+ " already exists. Overwriting.");
}
}
final InsetsSource source = new InsetsSource(id, provider.getType());
source.setFrame(provider.getArbitraryRectangle());
mLocalInsetsSources.put(id, source);
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
private class DeathRecipient implements IBinder.DeathRecipient {
private final IBinder mOwner;
private final ArraySet<Integer> mSourceIds = new ArraySet<>();
DeathRecipient(IBinder owner) {
mOwner = owner;
}
void addSourceId(int id) {
mSourceIds.add(id);
}
void removeSourceId(int id) {
mSourceIds.remove(id);
}
boolean hasSource() {
return !mSourceIds.isEmpty();
}
@Override
public void binderDied() {
synchronized (mWmService.mGlobalLock) {
boolean changed = false;
for (int i = mSourceIds.size() - 1; i >= 0; i--) {
changed |= removeLocalInsetsSource(mSourceIds.valueAt(i));
}
mSourceIds.clear();
mOwner.unlinkToDeath(this, 0);
mInsetsOwnerDeathRecipientMap.remove(mOwner);
if (changed && mDisplayContent != null) {
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
}
}
}
void removeLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) {
if (provider == null || owner == null) {
throw new IllegalArgumentException("Insets provider or owner not specified.");
}
final int id = provider.getId();
if (removeLocalInsetsSource(id) && mDisplayContent != null) {
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
if (mInsetsOwnerDeathRecipientMap == null) {
return;
}
final DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner);
if (deathRecipient == null) {
return;
}
deathRecipient.removeSourceId(id);
if (!deathRecipient.hasSource()) {
owner.unlinkToDeath(deathRecipient, 0);
mInsetsOwnerDeathRecipientMap.remove(owner);
}
}
private boolean removeLocalInsetsSource(int id) {
if (mLocalInsetsSources == null) {
return false;
}
if (mLocalInsetsSources.removeReturnOld(id) == null) {
if (DEBUG) {
Slog.d(TAG, "Given id " + Integer.toHexString(id) + " doesn't exist.");
}
return false;
}
return true;
}
/**
* Sets an {@link InsetsSourceProvider} to be associated with this {@code WindowContainer},
* but only if the provider itself is controllable, as one window can be the provider of more
* than one inset type (i.e. gesture insets). If this {code WindowContainer} is controllable,
* all its animations must be controlled by its control target, and the visibility of this
* {code WindowContainer} should be taken account into the state of the control target.
*
* @param insetProvider the provider which should not be visible to the client.
* @see WindowState#getInsetsState()
*/
void setControllableInsetProvider(InsetsSourceProvider insetProvider) {
mControllableInsetProvider = insetProvider;
}
InsetsSourceProvider getControllableInsetProvider() {
return mControllableInsetProvider;
}
@Override
final protected WindowContainer getParent() {
return mParent;
}
@Override
protected int getChildCount() {
return mChildren.size();
}
@Override
protected E getChildAt(int index) {
return mChildren.get(index);
}
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
super.onConfigurationChanged(newParentConfig);
updateSurfacePositionNonOrganized();
scheduleAnimation();
if (mOverlayHost != null) {
mOverlayHost.dispatchConfigurationChanged(getConfiguration());
}
}
void reparent(WindowContainer newParent, int position) {
if (newParent == null) {
throw new IllegalArgumentException("reparent: can't reparent to null " + this);
}
if (newParent == this) {
throw new IllegalArgumentException("Can not reparent to itself " + this);
}
final WindowContainer oldParent = mParent;
if (mParent == newParent) {
throw new IllegalArgumentException("WC=" + this + " already child of " + mParent);
}
// Collect before removing child from old parent, because the old parent may be removed if
// this is the last child in it.
mTransitionController.collectReparentChange(this, newParent);
// The display object before reparenting as that might lead to old parent getting removed
// from the display if it no longer has any child.
final DisplayContent prevDc = oldParent.getDisplayContent();
final DisplayContent dc = newParent.getDisplayContent();
mReparenting = true;
oldParent.removeChild(this);
newParent.addChild(this, position);
mReparenting = false;
// Relayout display(s)
dc.setLayoutNeeded();
if (prevDc != dc) {
onDisplayChanged(dc);
prevDc.setLayoutNeeded();
}
// Send onParentChanged notification here is we disabled sending it in setParent for
// reparenting case.
onParentChanged(newParent, oldParent);
onSyncReparent(oldParent, newParent);
}
final protected void setParent(WindowContainer<WindowContainer> parent) {
final WindowContainer oldParent = mParent;
mParent = parent;
if (mParent != null) {
mParent.onChildAdded(this);
} else if (mSurfaceAnimator.hasLeash()) {
mSurfaceAnimator.cancelAnimation();
}
if (!mReparenting) {
onSyncReparent(oldParent, mParent);
if (mParent != null && mParent.mDisplayContent != null
&& mDisplayContent != mParent.mDisplayContent) {
onDisplayChanged(mParent.mDisplayContent);
}
onParentChanged(mParent, oldParent);
}
}
/**
* Callback that is triggered when @link WindowContainer#setParent(WindowContainer)} was called.
* Supposed to be overridden and contain actions that should be executed after parent was set.
*/
@Override
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
super.onParentChanged(newParent, oldParent);
if (mParent == null) {
return;
}
if (mSurfaceControl == null) {
// If we don't yet have a surface, but we now have a parent, we should
// build a surface.
createSurfaceControl(false /*force*/);
} else {
// If we have a surface but a new parent, we just need to perform a reparent. Go through
// surface animator such that hierarchy is preserved when animating, i.e.
// mSurfaceControl stays attached to the leash and we just reparent the leash to the
// new parent.
reparentSurfaceControl(getSyncTransaction(), mParent.mSurfaceControl);
}
// Either way we need to ask the parent to assign us a Z-order.
mParent.assignChildLayers();
}
void createSurfaceControl(boolean force) {
setInitialSurfaceControlProperties(makeSurface());
}
void setInitialSurfaceControlProperties(Builder b) {
setSurfaceControl(b.setCallsite("WindowContainer.setInitialSurfaceControlProperties").build());
if (showSurfaceOnCreation()) {
getSyncTransaction().show(mSurfaceControl);
}
updateSurfacePositionNonOrganized();
if (mLastMagnificationSpec != null) {
applyMagnificationSpec(getSyncTransaction(), mLastMagnificationSpec);
}
}
/**
* Create a new SurfaceControl for this WindowContainer and migrate all properties to the new
* SurfaceControl. Properties include:
* 1. Children
* 2. Position
* 3. Z order
*
* Remove the old SurfaceControl since it's no longer needed.
*
* This is used to revoke control of the SurfaceControl from a client process that was
* previously organizing this WindowContainer.
*/
void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
t.remove(mSurfaceControl);
// Clear the last position so the new SurfaceControl will get correct position
mLastSurfacePosition.set(0, 0);
mLastDeltaRotation = Surface.ROTATION_0;
final Builder b = mWmService.makeSurfaceBuilder(null)
.setContainerLayer()
.setName(getName());
setInitialSurfaceControlProperties(b);
// If parent is null, the layer should be placed offscreen so reparent to null. Otherwise,
// set to the available parent.
t.reparent(mSurfaceControl, mParent == null ? null : mParent.getSurfaceControl());
if (mLastRelativeToLayer != null) {
t.setRelativeLayer(mSurfaceControl, mLastRelativeToLayer, mLastLayer);
} else {
t.setLayer(mSurfaceControl, mLastLayer);
}
for (int i = 0; i < mChildren.size(); i++) {
SurfaceControl sc = mChildren.get(i).getSurfaceControl();
if (sc != null) {
t.reparent(sc, mSurfaceControl);
}
}
if (mOverlayHost != null) {
mOverlayHost.setParent(t, mSurfaceControl);
}
scheduleAnimation();
}
// Temp. holders for a chain of containers we are currently processing.
private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList<>();
private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList<>();
/**
* Adds the input window container has a child of this container in order based on the input
* comparator.
* @param child The window container to add as a child of this window container.
* @param comparator Comparator to use in determining the position the child should be added to.
* If null, the child will be added to the top.
*/
@CallSuper
protected void addChild(E child, Comparator<E> comparator) {
if (!child.mReparenting && child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
+ " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
int positionToAdd = -1;
if (comparator != null) {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (comparator.compare(child, mChildren.get(i)) < 0) {
positionToAdd = i;
break;
}
}
}
if (positionToAdd == -1) {
mChildren.add(child);
} else {
mChildren.add(positionToAdd, child);
}
// Set the parent after we've actually added a child in case a subclass depends on this.
child.setParent(this);
}
/** Adds the input window container has a child of this container at the input index. */
@CallSuper
void addChild(E child, int index) {
if (!child.mReparenting && child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
+ " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName()
+ "\n callers=" + Debug.getCallers(15, "\n"));
}
if ((index < 0 && index != POSITION_BOTTOM)
|| (index > mChildren.size() && index != POSITION_TOP)) {
throw new IllegalArgumentException("addChild: invalid position=" + index
+ ", children number=" + mChildren.size());
}
if (index == POSITION_TOP) {
index = mChildren.size();
} else if (index == POSITION_BOTTOM) {
index = 0;
}
mChildren.add(index, child);
// Set the parent after we've actually added a child in case a subclass depends on this.
child.setParent(this);
}
private void onChildAdded(WindowContainer child) {
mTreeWeight += child.mTreeWeight;
WindowContainer parent = getParent();
while (parent != null) {
parent.mTreeWeight += child.mTreeWeight;
parent = parent.getParent();
}
onChildVisibleRequestedChanged(child);
onChildPositionChanged(child);
}
/**
* Removes the input child container from this container which is its parent.
*
* @return True if the container did contain the input child and it was detached.
*/
@CallSuper
void removeChild(E child) {
if (mChildren.remove(child)) {
onChildRemoved(child);
if (!child.mReparenting) {
child.setParent(null);
}
} else {
throw new IllegalArgumentException("removeChild: container=" + child.getName()
+ " is not a child of container=" + getName());
}
}
private void onChildRemoved(WindowContainer child) {
mTreeWeight -= child.mTreeWeight;
WindowContainer parent = getParent();
while (parent != null) {
parent.mTreeWeight -= child.mTreeWeight;
parent = parent.getParent();
}
onChildVisibleRequestedChanged(null);
onChildPositionChanged(child);
}
/**
* Removes this window container and its children with no regard for what else might be going on
* in the system. For example, the container will be removed during animation if this method is
* called which isn't desirable. For most cases you want to call {@link #removeIfPossible()}
* which allows the system to defer removal until a suitable time.
*/
@CallSuper
void removeImmediately() {
final DisplayContent dc = getDisplayContent();
if (dc != null) {
dc.mClosingChangingContainers.remove(this);
mSurfaceFreezer.unfreeze(getSyncTransaction());
}
while (!mChildren.isEmpty()) {
final E child = mChildren.peekLast();
child.removeImmediately();
// Need to do this after calling remove on the child because the child might try to
// remove/detach itself from its parent which will cause an exception if we remove
// it before calling remove on the child.
if (mChildren.remove(child)) {
onChildRemoved(child);
}
}
if (mSurfaceControl != null) {
getSyncTransaction().remove(mSurfaceControl);
setSurfaceControl(null);
mLastSurfacePosition.set(0, 0);
mLastDeltaRotation = Surface.ROTATION_0;
scheduleAnimation();
}
if (mOverlayHost != null) {
mOverlayHost.release();
mOverlayHost = null;
}
// This must happen after updating the surface so that sync transactions can be handled
// properly.
if (mParent != null) {
mParent.removeChild(this);
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onRemoved();
}
}
/** Returns the total number of descendants, including self. */
int getTreeWeight() {
return mTreeWeight;
}
/**
* @return The index of this element in the hierarchy tree in prefix order.
*/
int getPrefixOrderIndex() {
if (mParent == null) {
return 0;
}
return mParent.getPrefixOrderIndex(this);
}
private int getPrefixOrderIndex(WindowContainer child) {
int order = 0;
for (int i = 0; i < mChildren.size(); i++) {
final WindowContainer childI = mChildren.get(i);
if (child == childI) {
break;
}
order += childI.mTreeWeight;
}
if (mParent != null) {
order += mParent.getPrefixOrderIndex(this);
}
// We also need to count ourselves.
order++;
return order;
}
/**
* Removes this window container and its children taking care not to remove them during a
* critical stage in the system. For example, some containers will not be removed during
* animation if this method is called.
*/
// TODO: figure-out implementation that works best for this.
// E.g. when do we remove from parent list? maybe not...
void removeIfPossible() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
wc.removeIfPossible();
}
}
/** Returns true if this window container has the input child. */
boolean hasChild(E child) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final E current = mChildren.get(i);
if (current == child || current.hasChild(child)) {
return true;
}
}
return false;
}
/** @return true if this window container is a descendant of the input container. */
boolean isDescendantOf(WindowContainer ancestor) {
final WindowContainer parent = getParent();
if (parent == ancestor) return true;
return (parent != null) && parent.isDescendantOf(ancestor);
}
/**
* Move a child from it's current place in siblings list to the specified position,
* with an option to move all its parents to top.
* @param position Target position to move the child to.
* @param child Child to move to selected position.
* @param includingParents Flag indicating whether we need to move the entire branch of the
* hierarchy when we're moving a child to {@link #POSITION_TOP} or
* {@link #POSITION_BOTTOM}. When moving to other intermediate positions
* this flag will do nothing.
*/
@CallSuper
void positionChildAt(int position, E child, boolean includingParents) {
if (child.getParent() != this) {
throw new IllegalArgumentException("positionChildAt: container=" + child.getName()
+ " is not a child of container=" + getName()
+ " current parent=" + child.getParent());
}
if (position >= mChildren.size() - 1) {
position = POSITION_TOP;
} else if (position <= 0) {
position = POSITION_BOTTOM;
}
switch (position) {
case POSITION_TOP:
if (mChildren.peekLast() != child) {
mChildren.remove(child);
mChildren.add(child);
onChildPositionChanged(child);
}
if (includingParents && getParent() != null) {
getParent().positionChildAt(POSITION_TOP, this /* child */,
true /* includingParents */);
}
break;
case POSITION_BOTTOM:
if (mChildren.peekFirst() != child) {
mChildren.remove(child);
mChildren.addFirst(child);
onChildPositionChanged(child);
}
if (includingParents && getParent() != null) {
getParent().positionChildAt(POSITION_BOTTOM, this /* child */,
true /* includingParents */);
}
break;
default:
// TODO: Removing the child before reinserting requires the caller to provide a
// position that takes into account the removed child (if the index of the
// child < position, then the position should be adjusted). We should consider
// doing this adjustment here and remove any adjustments in the callers.
if (mChildren.indexOf(child) != position) {
mChildren.remove(child);
mChildren.add(position, child);
onChildPositionChanged(child);
}
}
}
/**
* Notify that a child's position has changed. Possible changes are adding or removing a child.
*/
void onChildPositionChanged(WindowContainer child) { }
/**
* Update override configuration and recalculate full config.
* @see #mRequestedOverrideConfiguration
* @see #mFullConfiguration
*/
@Override
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
// We must diff before the configuration is applied so that we can capture the change
// against the existing bounds.
final int diff = diffRequestedOverrideBounds(
overrideConfiguration.windowConfiguration.getBounds());
super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
if (mParent != null) {
mParent.onDescendantOverrideConfigurationChanged();
}
if (diff == BOUNDS_CHANGE_NONE) {
return;
}
if ((diff & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
onResize();
} else {
onMovedByResize();
}
}
/**
* Notify that a descendant's overrideConfiguration has changed.
*/
void onDescendantOverrideConfigurationChanged() {
if (mParent != null) {
mParent.onDescendantOverrideConfigurationChanged();
}
}
/**
* Notify that the display this container is on has changed. This could be either this container
* is moved to a new display, or some configurations on the display it is on changes.
*
* @param dc The display this container is on after changes.
*/
void onDisplayChanged(DisplayContent dc) {
if (mDisplayContent != null && mDisplayContent != dc) {
// Cancel any change transition queued-up for this container on the old display when
// this container is moved from the old display.
mDisplayContent.mClosingChangingContainers.remove(this);
if (mDisplayContent.mChangingContainers.remove(this)) {
mSurfaceFreezer.unfreeze(getSyncTransaction());
}
}
mDisplayContent = dc;
if (dc != null && dc != this) {
dc.getPendingTransaction().merge(mPendingTransaction);
}
if (dc != this && mLocalInsetsSources != null) {
mLocalInsetsSources.clear();
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mChildren.get(i);
child.onDisplayChanged(dc);
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onDisplayChanged(dc);
}
}
/**
* Returns {@code true} if this node provides insets.
*/
public boolean hasInsetsSourceProvider() {
return mInsetsSourceProviders != null;
}
/**
* Returns {@link InsetsSourceProvider}s provided by this node.
*/
public SparseArray<InsetsSourceProvider> getInsetsSourceProviders() {
if (mInsetsSourceProviders == null) {
mInsetsSourceProviders = new SparseArray<>();
}
return mInsetsSourceProviders;
}
public final DisplayContent getDisplayContent() {
return mDisplayContent;
}
/** Returns the first node of type {@link DisplayArea} above or at this node. */
@Nullable
DisplayArea getDisplayArea() {
WindowContainer parent = getParent();
return parent != null ? parent.getDisplayArea() : null;
}
/** Returns the first node of type {@link RootDisplayArea} above or at this node. */
@Nullable
RootDisplayArea getRootDisplayArea() {
WindowContainer parent = getParent();
return parent != null ? parent.getRootDisplayArea() : null;
}
@Nullable
TaskDisplayArea getTaskDisplayArea() {
WindowContainer parent = getParent();
return parent != null ? parent.getTaskDisplayArea() : null;
}
boolean isAttached() {
WindowContainer parent = getParent();
return parent != null && parent.isAttached();
}
void onResize() {
if (mControllableInsetProvider != null) {
mControllableInsetProvider.onWindowContainerBoundsChanged();
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
wc.onParentResize();
}
}
void onParentResize() {
// In the case this container has specified its own bounds, a parent resize will not
// affect its bounds. Any relevant changes will be propagated through changes to the
// Configuration override.
if (hasOverrideBounds()) {
return;
}
// Default implementation is to treat as resize on self.
onResize();
}
void onMovedByResize() {
if (mControllableInsetProvider != null) {
mControllableInsetProvider.onWindowContainerBoundsChanged();
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
wc.onMovedByResize();
}
}
void resetDragResizingChangeReported() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
wc.resetDragResizingChangeReported();
}
}
/**
* @return {@code true} when an application can override an app transition animation on this
* container.
*/
boolean canCustomizeAppTransition() {
return false;
}
/**
* @return {@code true} when this container or its related containers are running an
* animation, {@code false} otherwise.
*
* By default this predicate only checks if this container itself is actually running an
* animation, but you can extend the check target over its relatives, or relax the condition
* so that this can return {@code true} if an animation starts soon by giving a combination
* of {@link AnimationFlags}.
*
* Note that you can give a combination of bitmask flags to specify targets and condition for
* checking animating status.
* e.g. {@code isAnimating(TRANSITION | PARENT)} returns {@code true} if either this
* container itself or one of its parents is running an animation or waiting for an app
* transition.
*
* Note that TRANSITION propagates to parents and children as well.
*
* @param flags The combination of bitmask flags to specify targets and condition for
* checking animating status.
* @param typesToCheck The combination of bitmask {@link AnimationType} to compare when
* determining if animating.
*
* @see AnimationFlags#TRANSITION
* @see AnimationFlags#PARENTS
* @see AnimationFlags#CHILDREN
*/
final boolean isAnimating(int flags, int typesToCheck) {
return getAnimatingContainer(flags, typesToCheck) != null;
}
/**
* @deprecated Use {@link #isAnimating(int, int)}
* TODO (b/152333373): Migrate calls to use isAnimating with specified animation type
*/
@Deprecated
final boolean isAnimating(int flags) {
return isAnimating(flags, ANIMATION_TYPE_ALL);
}
/**
* @return {@code true} when the container is waiting the app transition start, {@code false}
* otherwise.
*/
boolean isWaitingForTransitionStart() {
return false;
}
/**
* @return {@code true} if in this subtree of the hierarchy we have an
* {@code ActivityRecord#isAnimating(TRANSITION)}, {@code false} otherwise.
*/
boolean isAppTransitioning() {
return getActivity(app -> app.isAnimating(PARENTS | TRANSITION)) != null;
}
/**
* Returns {@code true} if self or the parent container of the window is in transition, e.g.
* the app or recents transition. This method is only used when legacy and shell transition
* have the same condition to check the animation state.
*/
boolean inTransitionSelfOrParent() {
if (!mTransitionController.isShellTransitionsEnabled()) {
return isAnimating(PARENTS | TRANSITION,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
}
return inTransition();
}
/**
* @return Whether our own container running an animation at the moment.
*/
final boolean isAnimating() {
return isAnimating(0 /* self only */);
}
/**
* @return {@code true} if the container is in changing app transition.
*/
boolean isChangingAppTransition() {
return mDisplayContent != null && mDisplayContent.mChangingContainers.contains(this);
}
boolean inTransition() {
return mTransitionController.inTransition(this);
}
boolean isExitAnimationRunningSelfOrChild() {
if (!mTransitionController.isShellTransitionsEnabled()) {
return isAnimating(TRANSITION | CHILDREN, WindowState.EXIT_ANIMATING_TYPES);
}
// Only check leaf containers because inTransition() includes parent.
if (mChildren.isEmpty() && inTransition()) {
return true;
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
WindowContainer child = mChildren.get(i);
if (child.isExitAnimationRunningSelfOrChild()) {
return true;
}
}
return false;
}
void sendAppVisibilityToClients() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
wc.sendAppVisibilityToClients();
}
}
/**
* Returns true if the container or one of its children as some content it can display or wants
* to display (e.g. app views or saved surface).
*
* NOTE: While this method will return true if the there is some content to display, it doesn't
* mean the container is visible. Use {@link #isVisible()} to determine if the container is
* visible.
*/
boolean hasContentToDisplay() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
if (wc.hasContentToDisplay()) {
return true;
}
}
return false;
}
/**
* Returns true if the container or one of its children is considered visible from the
* WindowManager perspective which usually means valid surface and some other internal state
* are true.
*
* NOTE: While this method will return true if the surface is visible, it doesn't mean the
* client has actually displayed any content. Use {@link #hasContentToDisplay()} to determine if
* the container has any content to display.
*/
boolean isVisible() {
// TODO: Will this be more correct if it checks the visibility of its parents?
// It depends...For example, Tasks and Stacks are only visible if there children are visible
// but, WindowState are not visible if there parent are not visible. Maybe have the
// container specify which direction to traverse for visibility?
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
if (wc.isVisible()) {
return true;
}
}
return false;
}
/**
* Is this window's surface needed? This is almost like isVisible, except when participating
* in a transition, this will reflect the final visibility while isVisible won't change until
* the transition is finished.
*/
boolean isVisibleRequested() {
return mVisibleRequested;
}
/** @return `true` if visibleRequested changed. */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
boolean setVisibleRequested(boolean visible) {
if (mVisibleRequested == visible) return false;
mVisibleRequested = visible;
final WindowContainer parent = getParent();
if (parent != null) {
parent.onChildVisibleRequestedChanged(this);
}
// Notify listeners about visibility change.
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onVisibleRequestedChanged(mVisibleRequested);
}
return true;
}
/**
* @param child The changed or added child. `null` if a child was removed.
* @return `true` if visibleRequested changed.
*/
protected boolean onChildVisibleRequestedChanged(@Nullable WindowContainer child) {
final boolean childVisReq = child != null && child.isVisibleRequested();
boolean newVisReq = mVisibleRequested;
if (childVisReq && !mVisibleRequested) {
newVisReq = true;
} else if (!childVisReq && mVisibleRequested) {
newVisReq = false;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
if (wc != child && wc.isVisibleRequested()) {
newVisReq = true;
break;
}
}
}
return setVisibleRequested(newVisReq);
}
/**
* Called when the visibility of a child is asked to change. This is before visibility actually
* changes (eg. a transition animation might play out first).
*/
void onChildVisibilityRequested(boolean visible) {
// If we are losing visibility, then a snapshot isn't necessary and we are no-longer
// part of a change transition.
if (!visible) {
boolean skipUnfreeze = false;
if (asTaskFragment() != null) {
// If the organized TaskFragment is closing while resizing, we want to keep track of
// its starting bounds to make sure the animation starts at the correct position.
// This should be called before unfreeze() because we record the starting bounds
// in SurfaceFreezer.
skipUnfreeze = asTaskFragment().setClosingChangingStartBoundsIfNeeded();
}
if (!skipUnfreeze) {
mSurfaceFreezer.unfreeze(getSyncTransaction());
}
}
WindowContainer parent = getParent();
if (parent != null) {
parent.onChildVisibilityRequested(visible);
}
}
/** Whether this window is closing while resizing. */
boolean isClosingWhenResizing() {
return mDisplayContent != null
&& mDisplayContent.mClosingChangingContainers.containsKey(this);
}
void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(HASH_CODE, System.identityHashCode(this));
proto.write(USER_ID, USER_NULL);
proto.write(TITLE, "WindowContainer");
proto.end(token);
}
/**
* Returns {@code true} if this container is focusable. Generally, if a parent is not focusable,
* this will not be focusable either.
*/
boolean isFocusable() {
final WindowContainer parent = getParent();
return (parent == null || parent.isFocusable()) && mIsFocusable;
}
/** Set whether this container or its children can be focusable */
boolean setFocusable(boolean focusable) {
if (mIsFocusable == focusable) {
return false;
}
mIsFocusable = focusable;
return true;
}
/**
* @return Whether this child is on top of the window hierarchy.
*/
boolean isOnTop() {
final WindowContainer parent = getParent();
return parent != null && parent.getTopChild() == this && parent.isOnTop();
}
/** Returns the top child container. */
E getTopChild() {
return mChildren.peekLast();
}
/**
* Removes the containers which were deferred.
*
* @return {@code true} if there is still a removal being deferred.
*/
boolean handleCompleteDeferredRemoval() {
boolean stillDeferringRemoval = false;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
stillDeferringRemoval |= wc.handleCompleteDeferredRemoval();
if (!hasChild()) {
// All child containers of current level could be removed from a removal of
// descendant. E.g. if a display is pending to be removed because it contains an
// activity with {@link ActivityRecord#mIsExiting} is true, the display may be
// removed when completing the removal of the last activity from
// {@link ActivityRecord#handleCompleteDeferredRemoval}.
return false;
}
}
return stillDeferringRemoval;
}
/** Checks if all windows in an app are all drawn and shows them if needed. */
void checkAppWindowsReadyToShow() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
wc.checkAppWindowsReadyToShow();
}
}
void onAppTransitionDone() {
if (mSurfaceFreezer.hasLeash()) {
mSurfaceFreezer.unfreeze(getSyncTransaction());
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
wc.onAppTransitionDone();
}
}
/**
* Called when this container or one of its descendants changed its requested orientation, and
* wants this container to handle it or pass it to its parent.
*
* @param requestingContainer the container which orientation request has changed
* @return {@code true} if handled; {@code false} otherwise.
*/
boolean onDescendantOrientationChanged(@Nullable WindowContainer requestingContainer) {
final WindowContainer parent = getParent();
if (parent == null) {
return false;
}
return parent.onDescendantOrientationChanged(requestingContainer);
}
/**
* Check if this container or its parent will handle orientation changes from descendants. It's
* different from the return value of {@link #onDescendantOrientationChanged(WindowContainer)}
* in the sense that the return value of this method tells if this container or its parent will
* handle the request eventually, while the return value of the other method is if it handled
* the request synchronously.
*
* @return {@code true} if it handles or will handle orientation change in the future; {@code
* false} if it won't handle the change at anytime.
*/
boolean handlesOrientationChangeFromDescendant(int orientation) {
final WindowContainer parent = getParent();
return parent != null && parent.handlesOrientationChangeFromDescendant(orientation);
}
/**
* Gets the configuration orientation by the requested screen orientation
* ({@link ScreenOrientation}) of this activity.
*
* @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE},
* {@link Configuration#ORIENTATION_PORTRAIT},
* {@link Configuration#ORIENTATION_UNDEFINED}).
*/
@Configuration.Orientation
int getRequestedConfigurationOrientation() {
return getRequestedConfigurationOrientation(false /* forDisplay */);
}
/**
* Gets the configuration orientation by the requested screen orientation
* ({@link ScreenOrientation}) of this activity.
*
* @param forDisplay whether it is the requested config orientation for display.
* If {@code true}, we may reverse the requested orientation if the root is
* different from the display, so that when the display rotates to the
* reversed orientation, the requested app will be in the requested
* orientation.
* @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE},
* {@link Configuration#ORIENTATION_PORTRAIT},
* {@link Configuration#ORIENTATION_UNDEFINED}).
*/
@Configuration.Orientation
int getRequestedConfigurationOrientation(boolean forDisplay) {
return getRequestedConfigurationOrientation(forDisplay, getOverrideOrientation());
}
/**
* Gets the configuration orientation by the requested screen orientation
*
* @param forDisplay whether it is the requested config orientation for display.
* If {@code true}, we may reverse the requested orientation if the root is
* different from the display, so that when the display rotates to the
* reversed orientation, the requested app will be in the requested
* orientation.
* @param requestedOrientation the screen orientation({@link ScreenOrientation}) that is
* requested
* @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE},
* {@link Configuration#ORIENTATION_PORTRAIT},
* {@link Configuration#ORIENTATION_UNDEFINED}).
*/
@Configuration.Orientation
int getRequestedConfigurationOrientation(boolean forDisplay,
@ScreenOrientation int requestedOrientation) {
final RootDisplayArea root = getRootDisplayArea();
if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) {
// Reverse the requested orientation if the orientation of its root is different from
// the display, so that when the display rotates to the reversed orientation, the
// requested app will be in the requested orientation.
// For example, if the display is 1200x900 (landscape), and the DAG is 600x900
// (portrait).
// When an app below the DAG is requesting landscape, it should actually request the
// display to be portrait, so that the DAG and the app will be in landscape.
requestedOrientation = reverseOrientation(requestedOrientation);
}
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
// NOSENSOR means the display's "natural" orientation, so return that.
if (mDisplayContent != null) {
return mDisplayContent.getNaturalOrientation();
}
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
// LOCKED means the activity's orientation remains unchanged, so return existing value.
return getConfiguration().orientation;
} else if (isFixedOrientationLandscape(requestedOrientation)) {
return ORIENTATION_LANDSCAPE;
} else if (isFixedOrientationPortrait(requestedOrientation)) {
return ORIENTATION_PORTRAIT;
}
return ORIENTATION_UNDEFINED;
}
/**
* Calls {@link #setOrientation(int, WindowContainer)} with {@code null} to the last 2
* parameters.
*
* @param orientation the specified orientation.
*/
void setOrientation(@ScreenOrientation int orientation) {
setOrientation(orientation, null /* requestingContainer */);
}
/**
* Sets the specified orientation of this container. It percolates this change upward along the
* hierarchy to let each level of the hierarchy a chance to respond to it.
*
* @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}.
* @param requestingContainer the container which orientation request has changed. Mostly used
* to ensure it gets correct configuration.
*/
void setOrientation(@ScreenOrientation int orientation,
@Nullable WindowContainer requestingContainer) {
if (getOverrideOrientation() == orientation) {
return;
}
setOverrideOrientation(orientation);
final WindowContainer parent = getParent();
if (parent != null) {
if (getConfiguration().orientation != getRequestedConfigurationOrientation()
// Update configuration directly only if the change won't be dispatched from
// ancestor. This prevents from computing intermediate configuration when the
// parent also needs to be updated from the ancestor. E.g. the app requests
// portrait but the task is still in landscape. While updating from display,
// the task can be updated to portrait first so the configuration can be
// computed in a consistent environment.
&& (inMultiWindowMode()
|| !handlesOrientationChangeFromDescendant(orientation))) {
// Resolve the requested orientation.
onConfigurationChanged(parent.getConfiguration());
}
onDescendantOrientationChanged(requestingContainer);
}
}
@ScreenOrientation
int getOrientation() {
return getOrientation(getOverrideOrientation());
}
/**
* Returns the specified orientation for this window container or one of its children is there
* is one set, or {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSET} if no
* specification is set.
* NOTE: {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} is a
* specification...
*
* @param candidate The current orientation candidate that will be returned if we don't find a
* better match.
* @return The orientation as specified by this branch or the window hierarchy.
*/
@ScreenOrientation
int getOrientation(@ScreenOrientation int candidate) {
mLastOrientationSource = null;
if (!providesOrientation()) {
return SCREEN_ORIENTATION_UNSET;
}
// The container fills its parent so we can use it orientation if it has one
// specified; otherwise we prefer to use the orientation of its topmost child that has one
// specified and fall back on this container's unset or unspecified value as a candidate
// if none of the children have a better candidate for the orientation.
if (getOverrideOrientation() != SCREEN_ORIENTATION_UNSET
&& getOverrideOrientation() != SCREEN_ORIENTATION_UNSPECIFIED) {
mLastOrientationSource = this;
return getOverrideOrientation();
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
// TODO: Maybe mOverrideOrientation should default to SCREEN_ORIENTATION_UNSET vs.
// SCREEN_ORIENTATION_UNSPECIFIED?
final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND
? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET);
if (orientation == SCREEN_ORIENTATION_BEHIND) {
// container wants us to use the orientation of the container behind it. See if we
// can find one. Else return SCREEN_ORIENTATION_BEHIND so the caller can choose to
// look behind this container.
candidate = orientation;
mLastOrientationSource = wc;
continue;
}
if (orientation == SCREEN_ORIENTATION_UNSET) {
continue;
}
if (wc.providesOrientation() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
// Use the orientation if the container can provide or requested an explicit
// orientation that isn't SCREEN_ORIENTATION_UNSPECIFIED.
ProtoLog.v(WM_DEBUG_ORIENTATION, "%s is requesting orientation %d (%s)",
wc.toString(), orientation,
ActivityInfo.screenOrientationToString(orientation));
mLastOrientationSource = wc;
return orientation;
}
}
return candidate;
}
/**
* Returns orientation specified on this level of hierarchy without taking children into
* account, like {@link #getOrientation} does, allowing subclasses to override. See {@link
* ActivityRecord#getOverrideOrientation} for an example.
*/
@ScreenOrientation
protected int getOverrideOrientation() {
return mOverrideOrientation;
}
protected void setOverrideOrientation(@ScreenOrientation int orientation) {
mOverrideOrientation = orientation;
}
/**
* @return The deepest source which decides the orientation of this window container since the
* last time {@link #getOrientation(int) was called.
*/
@Nullable
WindowContainer getLastOrientationSource() {
final WindowContainer source = mLastOrientationSource;
if (source != null && source != this) {
final WindowContainer nextSource = source.getLastOrientationSource();
if (nextSource != null) {
return nextSource;
}
}
return source;
}
boolean providesOrientation() {
return fillsParent();
}
/**
* Returns true if this container is opaque and fills all the space made available by its parent
* container.
*
* NOTE: It is possible for this container to occupy more space than the parent has (or less),
* this is just a signal from the client to window manager stating its intent, but not what it
* actually does.
*/
boolean fillsParent() {
return false;
}
/** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */
static int computeScreenLayout(int sourceScreenLayout, int screenWidthDp,
int screenHeightDp) {
sourceScreenLayout = sourceScreenLayout
& (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
final int longSize = Math.max(screenWidthDp, screenHeightDp);
final int shortSize = Math.min(screenWidthDp, screenHeightDp);
return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
}
// TODO: Users would have their own window containers under the display container?
void switchUser(int userId) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).switchUser(userId);
}
}
/** Returns whether the window should be shown for current user. */
boolean showToCurrentUser() {
return true;
}
void forAllWindowContainers(Consumer<WindowContainer> callback) {
callback.accept(this);
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
mChildren.get(i).forAllWindowContainers(callback);
}
}
/**
* For all windows at or below this container call the callback.
* @param callback Calls the {@link ToBooleanFunction#apply} method for each window found and
* stops the search if {@link ToBooleanFunction#apply} returns true.
* @param traverseTopToBottom If true traverses the hierarchy from top-to-bottom in terms of
* z-order, else from bottom-to-top.
* @return True if the search ended before we reached the end of the hierarchy due to
* {@link ToBooleanFunction#apply} returning true.
*/
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
return true;
}
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
return true;
}
}
}
return false;
}
void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
ForAllWindowsConsumerWrapper wrapper = obtainConsumerWrapper(callback);
forAllWindows(wrapper, traverseTopToBottom);
wrapper.release();
}
boolean forAllActivities(Predicate<ActivityRecord> callback) {
return forAllActivities(callback, true /*traverseTopToBottom*/);
}
boolean forAllActivities(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllActivities(callback, traverseTopToBottom)) return true;
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (mChildren.get(i).forAllActivities(callback, traverseTopToBottom)) return true;
}
}
return false;
}
void forAllActivities(Consumer<ActivityRecord> callback) {
forAllActivities(callback, true /*traverseTopToBottom*/);
}
void forAllActivities(Consumer<ActivityRecord> callback, boolean traverseTopToBottom) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).forAllActivities(callback, traverseTopToBottom);
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
mChildren.get(i).forAllActivities(callback, traverseTopToBottom);
}
}
}
/**
* Process all activities in this branch of the tree.
*
* @param callback Called for each activity found.
* @param boundary We don't return activities via {@param callback} until we get to this node in
* the tree.
* @param includeBoundary If the boundary from be processed to return activities.
* @param traverseTopToBottom direction to traverse the tree.
* @return {@code true} if we ended the search before reaching the end of the tree.
*/
final boolean forAllActivities(Predicate<ActivityRecord> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom) {
return forAllActivities(
callback, boundary, includeBoundary, traverseTopToBottom, new boolean[1]);
}
private boolean forAllActivities(Predicate<ActivityRecord> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
boolean[] boundaryFound) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (processForAllActivitiesWithBoundary(callback, boundary, includeBoundary,
traverseTopToBottom, boundaryFound, mChildren.get(i))) {
return true;
}
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (processForAllActivitiesWithBoundary(callback, boundary, includeBoundary,
traverseTopToBottom, boundaryFound, mChildren.get(i))) {
return true;
}
}
}
return false;
}
private boolean processForAllActivitiesWithBoundary(Predicate<ActivityRecord> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
boolean[] boundaryFound, WindowContainer wc) {
if (wc == boundary) {
boundaryFound[0] = true;
if (!includeBoundary) return false;
}
if (boundaryFound[0]) {
return wc.forAllActivities(callback, traverseTopToBottom);
}
return wc.forAllActivities(
callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
}
/** @return {@code true} if this node or any of its children contains an activity. */
boolean hasActivity() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).hasActivity()) {
return true;
}
}
return false;
}
ActivityRecord getActivity(Predicate<ActivityRecord> callback) {
return getActivity(callback, true /*traverseTopToBottom*/);
}
ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
return getActivity(callback, traverseTopToBottom, null /*boundary*/);
}
ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
ActivityRecord boundary) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
// TODO(b/156986561): Improve the correctness of the boundary check.
if (wc == boundary) return boundary;
final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary);
if (r != null) {
return r;
}
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
final WindowContainer wc = mChildren.get(i);
// TODO(b/156986561): Improve the correctness of the boundary check.
if (wc == boundary) return boundary;
final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary);
if (r != null) {
return r;
}
}
}
return null;
}
/**
* Gets an activity in a branch of the tree.
*
* @param callback called to test if this is the activity that should be returned.
* @param boundary We don't return activities via {@param callback} until we get to this node in
* the tree.
* @param includeBoundary If the boundary from be processed to return activities.
* @param traverseTopToBottom direction to traverse the tree.
* @return The activity if found or null.
*/
final ActivityRecord getActivity(Predicate<ActivityRecord> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom) {
return getActivity(
callback, boundary, includeBoundary, traverseTopToBottom, new boolean[1]);
}
private ActivityRecord getActivity(Predicate<ActivityRecord> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
boolean[] boundaryFound) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final ActivityRecord r = processGetActivityWithBoundary(callback, boundary,
includeBoundary, traverseTopToBottom, boundaryFound, mChildren.get(i));
if (r != null) {
return r;
}
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
final ActivityRecord r = processGetActivityWithBoundary(callback, boundary,
includeBoundary, traverseTopToBottom, boundaryFound, mChildren.get(i));
if (r != null) {
return r;
}
}
}
return null;
}
int getDistanceFromTop(WindowContainer child) {
int idx = mChildren.indexOf(child);
return idx < 0 ? -1 : mChildren.size() - 1 - idx;
}
private ActivityRecord processGetActivityWithBoundary(Predicate<ActivityRecord> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
boolean[] boundaryFound, WindowContainer wc) {
if (wc == boundary || boundary == null) {
boundaryFound[0] = true;
if (!includeBoundary) return null;
}
if (boundaryFound[0]) {
return wc.getActivity(callback, traverseTopToBottom);
}
return wc.getActivity(
callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
}
ActivityRecord getActivityAbove(ActivityRecord r) {
return getActivity((above) -> true, r,
false /*includeBoundary*/, false /*traverseTopToBottom*/);
}
ActivityRecord getActivityBelow(ActivityRecord r) {
return getActivity((below) -> true, r,
false /*includeBoundary*/, true /*traverseTopToBottom*/);
}
ActivityRecord getBottomMostActivity() {
return getActivity((r) -> true, false /*traverseTopToBottom*/);
}
ActivityRecord getTopMostActivity() {
return getActivity((r) -> true, true /*traverseTopToBottom*/);
}
ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) {
// Break down into 4 calls to avoid object creation due to capturing input params.
if (includeFinishing) {
if (includeOverlays) {
return getActivity((r) -> true);
}
return getActivity((r) -> !r.isTaskOverlay());
} else if (includeOverlays) {
return getActivity((r) -> !r.finishing);
}
return getActivity((r) -> !r.finishing && !r.isTaskOverlay());
}
void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).forAllWallpaperWindows(callback);
}
}
/**
* For all tasks at or below this container call the callback.
*
* @param callback Calls the {@link ToBooleanFunction#apply} method for each task found and
* stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
*/
boolean forAllTasks(Predicate<Task> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllTasks(callback)) {
return true;
}
}
return false;
}
boolean forAllLeafTasks(Predicate<Task> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllLeafTasks(callback)) {
return true;
}
}
return false;
}
boolean forAllLeafTaskFragments(Predicate<TaskFragment> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllLeafTaskFragments(callback)) {
return true;
}
}
return false;
}
/**
* For all root tasks at or below this container call the callback.
*
* @param callback Calls the {@link ToBooleanFunction#apply} method for each root task found and
* stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
*/
boolean forAllRootTasks(Predicate<Task> callback) {
return forAllRootTasks(callback, true /* traverseTopToBottom */);
}
boolean forAllRootTasks(Predicate<Task> callback, boolean traverseTopToBottom) {
int count = mChildren.size();
if (traverseTopToBottom) {
for (int i = count - 1; i >= 0; --i) {
if (mChildren.get(i).forAllRootTasks(callback, traverseTopToBottom)) {
return true;
}
}
} else {
for (int i = 0; i < count; i++) {
if (mChildren.get(i).forAllRootTasks(callback, traverseTopToBottom)) {
return true;
}
// Root tasks may be removed from this display. Ensure each task will be processed
// and the loop will end.
int newCount = mChildren.size();
i -= count - newCount;
count = newCount;
}
}
return false;
}
/**
* For all tasks at or below this container call the callback.
*
* @param callback Callback to be called for every task.
*/
void forAllTasks(Consumer<Task> callback) {
forAllTasks(callback, true /*traverseTopToBottom*/);
}
void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
final int count = mChildren.size();
if (traverseTopToBottom) {
for (int i = count - 1; i >= 0; --i) {
mChildren.get(i).forAllTasks(callback, traverseTopToBottom);
}
} else {
for (int i = 0; i < count; i++) {
mChildren.get(i).forAllTasks(callback, traverseTopToBottom);
}
}
}
/**
* For all task fragments at or below this container call the callback.
*
* @param callback Callback to be called for every task.
*/
void forAllTaskFragments(Consumer<TaskFragment> callback) {
forAllTaskFragments(callback, true /*traverseTopToBottom*/);
}
void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
final int count = mChildren.size();
if (traverseTopToBottom) {
for (int i = count - 1; i >= 0; --i) {
mChildren.get(i).forAllTaskFragments(callback, traverseTopToBottom);
}
} else {
for (int i = 0; i < count; i++) {
mChildren.get(i).forAllTaskFragments(callback, traverseTopToBottom);
}
}
}
void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
final int count = mChildren.size();
if (traverseTopToBottom) {
for (int i = count - 1; i >= 0; --i) {
mChildren.get(i).forAllLeafTasks(callback, traverseTopToBottom);
}
} else {
for (int i = 0; i < count; i++) {
mChildren.get(i).forAllLeafTasks(callback, traverseTopToBottom);
}
}
}
void forAllLeafTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
final int count = mChildren.size();
if (traverseTopToBottom) {
for (int i = count - 1; i >= 0; --i) {
mChildren.get(i).forAllLeafTaskFragments(callback, traverseTopToBottom);
}
} else {
for (int i = 0; i < count; i++) {
mChildren.get(i).forAllLeafTaskFragments(callback, traverseTopToBottom);
}
}
}
/**
* For all root tasks at or below this container call the callback.
*
* @param callback Callback to be called for every root task.
*/
void forAllRootTasks(Consumer<Task> callback) {
forAllRootTasks(callback, true /* traverseTopToBottom */);
}
void forAllRootTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
int count = mChildren.size();
if (traverseTopToBottom) {
for (int i = count - 1; i >= 0; --i) {
mChildren.get(i).forAllRootTasks(callback, traverseTopToBottom);
}
} else {
for (int i = 0; i < count; i++) {
mChildren.get(i).forAllRootTasks(callback, traverseTopToBottom);
// Root tasks may be removed from this display. Ensure each task will be processed
// and the loop will end.
int newCount = mChildren.size();
i -= count - newCount;
count = newCount;
}
}
}
Task getTaskAbove(Task t) {
return getTask(
(above) -> true, t, false /*includeBoundary*/, false /*traverseTopToBottom*/);
}
Task getTaskBelow(Task t) {
return getTask((below) -> true, t, false /*includeBoundary*/, true /*traverseTopToBottom*/);
}
Task getBottomMostTask() {
return getTask((t) -> true, false /*traverseTopToBottom*/);
}
Task getTopMostTask() {
return getTask((t) -> true, true /*traverseTopToBottom*/);
}
Task getTask(Predicate<Task> callback) {
return getTask(callback, true /*traverseTopToBottom*/);
}
Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final Task t = mChildren.get(i).getTask(callback, traverseTopToBottom);
if (t != null) {
return t;
}
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
final Task t = mChildren.get(i).getTask(callback, traverseTopToBottom);
if (t != null) {
return t;
}
}
}
return null;
}
/**
* Gets an task in a branch of the tree.
*
* @param callback called to test if this is the task that should be returned.
* @param boundary We don't return tasks via {@param callback} until we get to this node in
* the tree.
* @param includeBoundary If the boundary from be processed to return tasks.
* @param traverseTopToBottom direction to traverse the tree.
* @return The task if found or null.
*/
final Task getTask(Predicate<Task> callback, WindowContainer boundary, boolean includeBoundary,
boolean traverseTopToBottom) {
return getTask(callback, boundary, includeBoundary, traverseTopToBottom, new boolean[1]);
}
private Task getTask(Predicate<Task> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
boolean[] boundaryFound) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final Task t = processGetTaskWithBoundary(callback, boundary,
includeBoundary, traverseTopToBottom, boundaryFound, mChildren.get(i));
if (t != null) {
return t;
}
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
final Task t = processGetTaskWithBoundary(callback, boundary,
includeBoundary, traverseTopToBottom, boundaryFound, mChildren.get(i));
if (t != null) {
return t;
}
}
}
return null;
}
/**
* Gets a root task in a branch of the tree.
*
* @param callback called to test if this is the task that should be returned.
* @return The root task if found or null.
*/
@Nullable
Task getRootTask(Predicate<Task> callback) {
return getRootTask(callback, true /*traverseTopToBottom*/);
}
@Nullable
Task getRootTask(Predicate<Task> callback, boolean traverseTopToBottom) {
int count = mChildren.size();
if (traverseTopToBottom) {
for (int i = count - 1; i >= 0; --i) {
final Task t = mChildren.get(i).getRootTask(callback, traverseTopToBottom);
if (t != null) {
return t;
}
}
} else {
for (int i = 0; i < count; i++) {
final Task t = mChildren.get(i).getRootTask(callback, traverseTopToBottom);
if (t != null) {
return t;
}
// Root tasks may be removed from this display. Ensure each task will be processed
// and the loop will end.
int newCount = mChildren.size();
i -= count - newCount;
count = newCount;
}
}
return null;
}
private Task processGetTaskWithBoundary(Predicate<Task> callback,
WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
boolean[] boundaryFound, WindowContainer wc) {
if (wc == boundary || boundary == null) {
boundaryFound[0] = true;
if (!includeBoundary) return null;
}
if (boundaryFound[0]) {
return wc.getTask(callback, traverseTopToBottom);
}
return wc.getTask(
callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
}
@Nullable
TaskFragment getTaskFragment(Predicate<TaskFragment> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final TaskFragment tf = mChildren.get(i).getTaskFragment(callback);
if (tf != null) {
return tf;
}
}
return null;
}
WindowState getWindow(Predicate<WindowState> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState w = mChildren.get(i).getWindow(callback);
if (w != null) {
return w;
}
}
return null;
}
void forAllDisplayAreas(Consumer<DisplayArea> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).forAllDisplayAreas(callback);
}
}
/**
* For all {@link TaskDisplayArea} at or below this container call the callback.
* @param callback Applies on each {@link TaskDisplayArea} found and stops the search if it
* returns {@code true}.
* @param traverseTopToBottom If {@code true}, traverses the hierarchy from top-to-bottom in
* terms of z-order, else from bottom-to-top.
* @return {@code true} if the search ended before we reached the end of the hierarchy due to
* callback returning {@code true}.
*/
boolean forAllTaskDisplayAreas(Predicate<TaskDisplayArea> callback,
boolean traverseTopToBottom) {
int childCount = mChildren.size();
int i = traverseTopToBottom ? childCount - 1 : 0;
while (i >= 0 && i < childCount) {
if (mChildren.get(i).forAllTaskDisplayAreas(callback, traverseTopToBottom)) {
return true;
}
i += traverseTopToBottom ? -1 : 1;
}
return false;
}
/**
* For all {@link TaskDisplayArea} at or below this container call the callback. Traverses from
* top to bottom in terms of z-order.
* @param callback Applies on each {@link TaskDisplayArea} found and stops the search if it
* returns {@code true}.
* @return {@code true} if the search ended before we reached the end of the hierarchy due to
* callback returning {@code true}.
*/
boolean forAllTaskDisplayAreas(Predicate<TaskDisplayArea> callback) {
return forAllTaskDisplayAreas(callback, true /* traverseTopToBottom */);
}
/**
* For all {@link TaskDisplayArea} at or below this container call the callback.
* @param callback Applies on each {@link TaskDisplayArea} found.
* @param traverseTopToBottom If {@code true}, traverses the hierarchy from top-to-bottom in
* terms of z-order, else from bottom-to-top.
*/
void forAllTaskDisplayAreas(Consumer<TaskDisplayArea> callback, boolean traverseTopToBottom) {
int childCount = mChildren.size();
int i = traverseTopToBottom ? childCount - 1 : 0;
while (i >= 0 && i < childCount) {
mChildren.get(i).forAllTaskDisplayAreas(callback, traverseTopToBottom);
i += traverseTopToBottom ? -1 : 1;
}
}
/**
* For all {@link TaskDisplayArea} at or below this container call the callback. Traverses from
* top to bottom in terms of z-order.
* @param callback Applies on each {@link TaskDisplayArea} found.
*/
void forAllTaskDisplayAreas(Consumer<TaskDisplayArea> callback) {
forAllTaskDisplayAreas(callback, true /* traverseTopToBottom */);
}
/**
* Performs a reduction on all {@link TaskDisplayArea} at or below this container, using the
* provided initial value and an accumulation function, and returns the reduced value.
* @param accumulator Applies on each {@link TaskDisplayArea} found with the accumulative result
* from the previous call.
* @param initValue The initial value to pass to the accumulating function with the first
* {@link TaskDisplayArea}.
* @param traverseTopToBottom If {@code true}, traverses the hierarchy from top-to-bottom in
* terms of z-order, else from bottom-to-top.
* @return the accumulative result.
*/
@Nullable
<R> R reduceOnAllTaskDisplayAreas(BiFunction<TaskDisplayArea, R, R> accumulator,
@Nullable R initValue, boolean traverseTopToBottom) {
int childCount = mChildren.size();
int i = traverseTopToBottom ? childCount - 1 : 0;
R result = initValue;
while (i >= 0 && i < childCount) {
result = (R) mChildren.get(i)
.reduceOnAllTaskDisplayAreas(accumulator, result, traverseTopToBottom);
i += traverseTopToBottom ? -1 : 1;
}
return result;
}
/**
* Performs a reduction on all {@link TaskDisplayArea} at or below this container, using the
* provided initial value and an accumulation function, and returns the reduced value. Traverses
* from top to bottom in terms of z-order.
* @param accumulator Applies on each {@link TaskDisplayArea} found with the accumulative result
* from the previous call.
* @param initValue The initial value to pass to the accumulating function with the first
* {@link TaskDisplayArea}.
* @return the accumulative result.
*/
@Nullable
<R> R reduceOnAllTaskDisplayAreas(BiFunction<TaskDisplayArea, R, R> accumulator,
@Nullable R initValue) {
return reduceOnAllTaskDisplayAreas(accumulator, initValue, true /* traverseTopToBottom */);
}
/**
* Finds the first non {@code null} return value from calling the callback on all
* {@link DisplayArea} at or below this container. Traverses from top to bottom in terms of
* z-order.
* @param callback Applies on each {@link DisplayArea} found and stops the search if it
* returns non {@code null}.
* @return the first returned object that is not {@code null}. Returns {@code null} if not
* found.
*/
@Nullable
<R> R getItemFromDisplayAreas(Function<DisplayArea, R> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
R result = (R) mChildren.get(i).getItemFromDisplayAreas(callback);
if (result != null) {
return result;
}
}
return null;
}
/**
* Finds the first non {@code null} return value from calling the callback on all
* {@link TaskDisplayArea} at or below this container.
* @param callback Applies on each {@link TaskDisplayArea} found and stops the search if it
* returns non {@code null}.
* @param traverseTopToBottom If {@code true}, traverses the hierarchy from top-to-bottom in
* terms of z-order, else from bottom-to-top.
* @return the first returned object that is not {@code null}. Returns {@code null} if not
* found.
*/
@Nullable
<R> R getItemFromTaskDisplayAreas(Function<TaskDisplayArea, R> callback,
boolean traverseTopToBottom) {
int childCount = mChildren.size();
int i = traverseTopToBottom ? childCount - 1 : 0;
while (i >= 0 && i < childCount) {
R result = (R) mChildren.get(i)
.getItemFromTaskDisplayAreas(callback, traverseTopToBottom);
if (result != null) {
return result;
}
i += traverseTopToBottom ? -1 : 1;
}
return null;
}
/**
* Finds the first non {@code null} return value from calling the callback on all
* {@link TaskDisplayArea} at or below this container. Traverses from top to bottom in terms of
* z-order.
* @param callback Applies on each {@link TaskDisplayArea} found and stops the search if it
* returns non {@code null}.
* @return the first returned object that is not {@code null}. Returns {@code null} if not
* found.
*/
@Nullable
<R> R getItemFromTaskDisplayAreas(Function<TaskDisplayArea, R> callback) {
return getItemFromTaskDisplayAreas(callback, true /* traverseTopToBottom */);
}
/**
* Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than
* the input container in terms of z-order.
*/
@Override
public int compareTo(WindowContainer other) {
if (this == other) {
return 0;
}
if (mParent != null && mParent == other.mParent) {
final WindowList<WindowContainer> list = mParent.mChildren;
return list.indexOf(this) > list.indexOf(other) ? 1 : -1;
}
final LinkedList<WindowContainer> thisParentChain = mTmpChain1;
final LinkedList<WindowContainer> otherParentChain = mTmpChain2;
try {
getParents(thisParentChain);
other.getParents(otherParentChain);
// Find the common ancestor of both containers.
WindowContainer commonAncestor = null;
WindowContainer thisTop = thisParentChain.peekLast();
WindowContainer otherTop = otherParentChain.peekLast();
while (thisTop != null && otherTop != null && thisTop == otherTop) {
commonAncestor = thisParentChain.removeLast();
otherParentChain.removeLast();
thisTop = thisParentChain.peekLast();
otherTop = otherParentChain.peekLast();
}
// Containers don't belong to the same hierarchy???
if (commonAncestor == null) {
throw new IllegalArgumentException("No in the same hierarchy this="
+ thisParentChain + " other=" + otherParentChain);
}
// Children are always considered greater than their parents, so if one of the containers
// we are comparing it the parent of the other then whichever is the child is greater.
if (commonAncestor == this) {
return -1;
} else if (commonAncestor == other) {
return 1;
}
// The position of the first non-common ancestor in the common ancestor list determines
// which is greater the which.
final WindowList<WindowContainer> list = commonAncestor.mChildren;
return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
? 1 : -1;
} finally {
mTmpChain1.clear();
mTmpChain2.clear();
}
}
private void getParents(LinkedList<WindowContainer> parents) {
parents.clear();
WindowContainer current = this;
do {
parents.addLast(current);
current = current.mParent;
} while (current != null);
}
Builder makeSurface() {
final WindowContainer p = getParent();
return p.makeChildSurface(this);
}
/**
* @param child The WindowContainer this child surface is for, or null if the Surface
* is not assosciated with a WindowContainer (e.g. a surface used for Dimming).
*/
Builder makeChildSurface(WindowContainer child) {
final WindowContainer p = getParent();
// Give the parent a chance to set properties. In hierarchy v1 we rely
// on this to set full-screen dimensions on all our Surface-less Layers.
return p.makeChildSurface(child)
.setParent(mSurfaceControl);
}
/*
* @return The SurfaceControl parent for this containers SurfaceControl.
* The SurfaceControl must be valid if non-null.
*/
@Override
public SurfaceControl getParentSurfaceControl() {
final WindowContainer parent = getParent();
if (parent == null) {
return null;
}
return parent.getSurfaceControl();
}
/**
* @return Whether this WindowContainer should be magnified by the accessibility magnifier.
*/
boolean shouldMagnify() {
if (mSurfaceControl == null) {
return false;
}
for (int i = 0; i < mChildren.size(); i++) {
if (!mChildren.get(i).shouldMagnify()) {
return false;
}
}
return true;
}
SurfaceSession getSession() {
if (getParent() != null) {
return getParent().getSession();
}
return null;
}
void assignLayer(Transaction t, int layer) {
// Don't assign layers while a transition animation is playing
// TODO(b/173528115): establish robust best-practices around z-order fighting.
if (!mTransitionController.canAssignLayers(this)) return;
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
if (mSurfaceControl != null && changed) {
setLayer(t, layer);
mLastLayer = layer;
mLastRelativeToLayer = null;
}
}
void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer,
boolean forceUpdate) {
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != relativeTo;
if (mSurfaceControl != null && (changed || forceUpdate)) {
setRelativeLayer(t, relativeTo, layer);
mLastLayer = layer;
mLastRelativeToLayer = relativeTo;
}
}
void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
assignRelativeLayer(t, relativeTo, layer, false /* forceUpdate */);
}
protected void setLayer(Transaction t, int layer) {
if (mSurfaceFreezer.hasLeash()) {
// When the freezer has created animation leash parent for the window, set the layer
// there instead.
mSurfaceFreezer.setLayer(t, layer);
} else {
// Route through surface animator to accommodate that our surface control might be
// attached to the leash, and leash is attached to parent container.
mSurfaceAnimator.setLayer(t, layer);
}
}
int getLastLayer() {
return mLastLayer;
}
SurfaceControl getLastRelativeLayer() {
return mLastRelativeToLayer;
}
protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
if (mSurfaceFreezer.hasLeash()) {
// When the freezer has created animation leash parent for the window, set the layer
// there instead.
mSurfaceFreezer.setRelativeLayer(t, relativeTo, layer);
} else {
// Route through surface animator to accommodate that our surface control might be
// attached to the leash, and leash is attached to parent container.
mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
}
}
protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
// Don't reparent active leashes since the animator won't know about the change.
if (mSurfaceFreezer.hasLeash() || mSurfaceAnimator.hasLeash()) return;
t.reparent(getSurfaceControl(), newParent);
}
void assignChildLayers(Transaction t) {
int layer = 0;
// We use two passes as a way to promote children which
// need Z-boosting to the end of the list.
for (int j = 0; j < mChildren.size(); ++j) {
final WindowContainer wc = mChildren.get(j);
wc.assignChildLayers(t);
if (!wc.needsZBoost()) {
wc.assignLayer(t, layer++);
}
}
for (int j = 0; j < mChildren.size(); ++j) {
final WindowContainer wc = mChildren.get(j);
if (wc.needsZBoost()) {
wc.assignLayer(t, layer++);
}
}
if (mOverlayHost != null) {
mOverlayHost.setLayer(t, layer++);
}
}
void assignChildLayers() {
assignChildLayers(getSyncTransaction());
scheduleAnimation();
}
boolean needsZBoost() {
if (mNeedsZBoost) return true;
for (int i = 0; i < mChildren.size(); i++) {
if (mChildren.get(i).needsZBoost()) {
return true;
}
}
return false;
}
/**
* Write to a protocol buffer output stream. Protocol buffer message definition is at
* {@link com.android.server.wm.WindowContainerProto}.
*
* @param proto Stream to write the WindowContainer object to.
* @param fieldId Field Id of the WindowContainer as defined in the parent message.
* @param logLevel Determines the amount of data to be written to the Protobuf.
* @hide
*/
@CallSuper
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
boolean isVisible = isVisible();
if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible) {
return;
}
final long token = proto.start(fieldId);
super.dumpDebug(proto, CONFIGURATION_CONTAINER, logLevel);
proto.write(ORIENTATION, mOverrideOrientation);
proto.write(VISIBLE, isVisible);
writeIdentifierToProto(proto, IDENTIFIER);
if (mSurfaceAnimator.isAnimating()) {
mSurfaceAnimator.dumpDebug(proto, SURFACE_ANIMATOR);
}
if (mSurfaceControl != null) {
mSurfaceControl.dumpDebug(proto, SURFACE_CONTROL);
}
// add children to proto
for (int i = 0; i < getChildCount(); i++) {
final long childToken = proto.start(WindowContainerProto.CHILDREN);
final E child = getChildAt(i);
child.dumpDebug(proto, child.getProtoFieldId(), logLevel);
proto.end(childToken);
}
proto.end(token);
}
/**
* @return a proto field id to identify where to add the derived class to the generic window
* container proto.
*/
long getProtoFieldId() {
return WINDOW_CONTAINER;
}
private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) {
ForAllWindowsConsumerWrapper wrapper = mConsumerWrapperPool.acquire();
if (wrapper == null) {
wrapper = new ForAllWindowsConsumerWrapper();
}
wrapper.setConsumer(consumer);
return wrapper;
}
private final class ForAllWindowsConsumerWrapper implements ToBooleanFunction<WindowState> {
private Consumer<WindowState> mConsumer;
void setConsumer(Consumer<WindowState> consumer) {
mConsumer = consumer;
}
@Override
public boolean apply(WindowState w) {
mConsumer.accept(w);
return false;
}
void release() {
mConsumer = null;
mConsumerWrapperPool.release(this);
}
}
// TODO(b/68336570): Should this really be on WindowContainer since it
// can only be used on the top-level nodes that aren't animated?
// (otherwise we would be fighting other callers of setMatrix).
void applyMagnificationSpec(Transaction t, MagnificationSpec spec) {
if (shouldMagnify()) {
t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
.setPosition(mSurfaceControl, spec.offsetX + mLastSurfacePosition.x,
spec.offsetY + mLastSurfacePosition.y);
mLastMagnificationSpec = spec;
} else {
clearMagnificationSpec(t);
for (int i = 0; i < mChildren.size(); i++) {
mChildren.get(i).applyMagnificationSpec(t, spec);
}
}
}
void clearMagnificationSpec(Transaction t) {
if (mLastMagnificationSpec != null) {
t.setMatrix(mSurfaceControl, 1, 0, 0, 1)
.setPosition(mSurfaceControl, mLastSurfacePosition.x, mLastSurfacePosition.y);
}
mLastMagnificationSpec = null;
for (int i = 0; i < mChildren.size(); i++) {
mChildren.get(i).clearMagnificationSpec(t);
}
}
void prepareSurfaces() {
// If a leash has been set when the transaction was committed, then the leash reparent has
// been committed.
mCommittedReparentToAnimationLeash = mSurfaceAnimator.hasLeash();
for (int i = 0; i < mChildren.size(); i++) {
mChildren.get(i).prepareSurfaces();
}
}
/**
* @return true if the reparent to animation leash transaction has been committed, false
* otherwise.
*/
boolean hasCommittedReparentToAnimationLeash() {
return mCommittedReparentToAnimationLeash;
}
/**
* Trigger a call to prepareSurfaces from the animation thread, such that pending transactions
* will be applied.
*/
void scheduleAnimation() {
mWmService.scheduleAnimationLocked();
}
/**
* @return The SurfaceControl for this container.
* The SurfaceControl must be valid if non-null.
*/
@Override
public SurfaceControl getSurfaceControl() {
return mSurfaceControl;
}
/**
* Use this method instead of {@link #getPendingTransaction()} if the Transaction should be
* synchronized with the client.
*
* @return {@link #mBLASTSyncTransaction} if available. Otherwise, returns
* {@link #getPendingTransaction()}
*/
@Override
public Transaction getSyncTransaction() {
if (mSyncTransactionCommitCallbackDepth > 0) {
return mSyncTransaction;
}
if (mSyncState != SYNC_STATE_NONE) {
return mSyncTransaction;
}
return getPendingTransaction();
}
@Override
public Transaction getPendingTransaction() {
final DisplayContent displayContent = getDisplayContent();
if (displayContent != null && displayContent != this) {
return displayContent.getPendingTransaction();
}
// This WindowContainer has not attached to a display yet or this is a DisplayContent, so we
// let the caller to save the surface operations within the local mPendingTransaction.
// If this is not a DisplayContent, we will merge it to the pending transaction of its
// display once it attaches to it.
return mPendingTransaction;
}
/**
* Starts an animation on the container.
*
* @param anim The animation to run.
* @param hidden Whether our container is currently hidden. TODO This should use isVisible at
* some point but the meaning is too weird to work for all containers.
* @param type The type of animation defined as {@link AnimationType}.
* @param animationFinishedCallback The callback being triggered when the animation finishes.
* @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
* cancel call to the underlying AnimationAdapter.
* @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no
* snapshot.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
@Nullable AnimationAdapter snapshotAnim) {
ProtoLog.v(WM_DEBUG_ANIM, "Starting animation on %s: type=%d, anim=%s",
this, type, anim);
// TODO: This should use isVisible() but because isVisible has a really weird meaning at
// the moment this doesn't work for all animatable window containers.
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback) {
startAnimation(t, anim, hidden, type, animationFinishedCallback,
null /* adapterAnimationCancelledCallback */, null /* snapshotAnim */);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type) {
startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
}
void transferAnimation(WindowContainer from) {
mSurfaceAnimator.transferAnimation(from.mSurfaceAnimator);
}
void cancelAnimation() {
doAnimationFinished(mSurfaceAnimator.getAnimationType(), mSurfaceAnimator.getAnimation());
mSurfaceAnimator.cancelAnimation();
mSurfaceFreezer.unfreeze(getSyncTransaction());
}
/** Whether we can start change transition with this window and current display status. */
boolean canStartChangeTransition() {
if (mWmService.mDisableTransitionAnimation || !okToAnimate()) return false;
// Change transition only make sense as we go from "visible" to "visible".
if (mDisplayContent == null || getSurfaceControl() == null
|| !isVisible() || !isVisibleRequested()) {
return false;
}
// Make sure display isn't a part of the transition already - needed for legacy transitions.
if (mDisplayContent.inTransition()) return false;
if (!ActivityTaskManagerService.isPip2ExperimentEnabled()) {
// Screenshots are turned off when PiP is undergoing changes.
return !inPinnedWindowingMode() && getParent() != null
&& !getParent().inPinnedWindowingMode();
}
return true;
}
/**
* Initializes a change transition. See {@link SurfaceFreezer} for more information.
*
* For now, this will only be called for the following cases:
* 1. {@link Task} is changing windowing mode between fullscreen and freeform.
* 2. {@link TaskFragment} is organized and is changing window bounds.
* 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}. (The
* transition will happen on the {@link TaskFragment} for this case).
*
* This shouldn't be called on other {@link WindowContainer} unless there is a valid
* use case.
*
* @param startBounds The original bounds (on screen) of the surface we are snapshotting.
* @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a
* snapshot from {@link #getFreezeSnapshotTarget()}.
*/
void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
mDisplayContent.mTransitionController.collectVisibleChange(this);
return;
}
mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
mDisplayContent.mChangingContainers.add(this);
// Calculate the relative position in parent container.
final Rect parentBounds = getParent().getBounds();
mTmpPoint.set(startBounds.left - parentBounds.left, startBounds.top - parentBounds.top);
mSurfaceFreezer.freeze(getSyncTransaction(), startBounds, mTmpPoint, freezeTarget);
}
void initializeChangeTransition(Rect startBounds) {
initializeChangeTransition(startBounds, null /* freezeTarget */);
}
ArraySet<WindowContainer> getAnimationSources() {
return mSurfaceAnimationSources;
}
@Override
public SurfaceControl getFreezeSnapshotTarget() {
// Only allow freezing if this window is in a TRANSIT_CHANGE
if (!mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CHANGE)
|| !mDisplayContent.mChangingContainers.contains(this)) {
return null;
}
return getSurfaceControl();
}
@Override
public void onUnfrozen() {
if (mDisplayContent != null) {
mDisplayContent.mChangingContainers.remove(this);
}
}
@Override
public Builder makeAnimationLeash() {
return makeSurface().setContainerLayer();
}
@Override
public SurfaceControl getAnimationLeashParent() {
return getParentSurfaceControl();
}
// TODO: Remove this and use #getBounds() instead once we set an app transition animation
// on TaskStack.
Rect getAnimationBounds(int appRootTaskClipMode) {
return getBounds();
}
/** Gets the position relative to parent for animation. */
void getAnimationPosition(Point outPosition) {
getRelativePosition(outPosition);
}
/**
* Applies the app transition animation according the given the layout properties in the
* window hierarchy.
*
* @param lp The layout parameters of the window.
* @param transit The app transition type indicates what kind of transition to be applied.
* @param enter Whether the app transition is entering transition or not.
* @param isVoiceInteraction Whether the container is participating in voice interaction or not.
* @param sources {@link ActivityRecord}s which causes this app transition animation.
*
* @return {@code true} when the container applied the app transition, {@code false} if the
* app transition is disabled or skipped.
*
* @see #getAnimationAdapter
*/
boolean applyAnimation(WindowManager.LayoutParams lp, @TransitionOldType int transit,
boolean enter, boolean isVoiceInteraction,
@Nullable ArrayList<WindowContainer> sources) {
if (mWmService.mDisableTransitionAnimation) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: transition animation is disabled or skipped. "
+ "container=%s", this);
cancelAnimation();
return false;
}
// Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
// to animate and it can cause strange artifacts when we unfreeze the display if some
// different animation is running.
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
if (okToAnimate()) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: transit=%s, enter=%b, wc=%s",
AppTransition.appTransitionOldToString(transit), enter, this);
applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
} else {
cancelAnimation();
}
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
return isAnimating();
}
/**
* Gets the {@link AnimationAdapter} according the given window layout properties in the window
* hierarchy.
*
* @return The return value will always contain two elements, one for normal animations and the
* other for thumbnail animation, both can be {@code null}.
*
* @See com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord
* @See LocalAnimationAdapter
*/
Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp,
@TransitionOldType int transit, boolean enter, boolean isVoiceInteraction) {
final Pair<AnimationAdapter, AnimationAdapter> resultAdapters;
final int appRootTaskClipMode = getDisplayContent().mAppTransition.getAppRootTaskClipMode();
// Separate position and size for use in animators.
final Rect screenBounds = getAnimationBounds(appRootTaskClipMode);
mTmpRect.set(screenBounds);
if (this.asTask() != null && isTaskTransitOld(transit)) {
this.asTask().adjustAnimationBoundsForTransition(mTmpRect);
}
getAnimationPosition(mTmpPoint);
mTmpRect.offsetTo(0, 0);
final AppTransition appTransition = getDisplayContent().mAppTransition;
final RemoteAnimationController controller = appTransition.getRemoteAnimationController();
final boolean isChanging = AppTransition.isChangeTransitOld(transit) && enter
&& isChangingAppTransition();
// Delaying animation start isn't compatible with remote animations at all.
if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
// Here we load App XML in order to read com.android.R.styleable#Animation_showBackdrop.
boolean showBackdrop = false;
// Optionally set backdrop color if App explicitly provides it through
// {@link Activity#overridePendingTransition(int, int, int)}.
@ColorInt int backdropColor = 0;
if (controller.isFromActivityEmbedding()) {
if (isChanging) {
// When there are more than one changing containers, it may leave part of the
// screen empty. Show background color to cover that.
showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
backdropColor = appTransition.getNextAppTransitionBackgroundColor();
} else {
// Check whether the app has requested to show backdrop for open/close
// transition.
final Animation a = appTransition.getNextAppRequestedAnimation(enter);
if (a != null) {
showBackdrop = a.getShowBackdrop();
backdropColor = a.getBackdropColor();
}
}
}
final Rect localBounds = new Rect(mTmpRect);
localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
final RemoteAnimationController.RemoteAnimationRecord adapters;
if (!isChanging && !enter && isClosingWhenResizing()) {
// Container that is closing while resizing. Pass in the closing start bounds, so
// the animation can start with the correct bounds, there won't be a snapshot.
// Cleanup the mClosingChangingContainers so that when the animation is finished, it
// will reset the surface.
final Rect closingStartBounds = getDisplayContent().mClosingChangingContainers
.remove(this);
adapters = controller.createRemoteAnimationRecord(
this, mTmpPoint, localBounds, screenBounds, closingStartBounds,
showBackdrop, false /* shouldCreateSnapshot */);
} else {
final Rect startBounds = isChanging ? mSurfaceFreezer.mFreezeBounds : null;
adapters = controller.createRemoteAnimationRecord(
this, mTmpPoint, localBounds, screenBounds, startBounds, showBackdrop);
}
if (backdropColor != 0) {
adapters.setBackDropColor(backdropColor);
}
if (!isChanging) {
adapters.setMode(enter
? RemoteAnimationTarget.MODE_OPENING
: RemoteAnimationTarget.MODE_CLOSING);
}
resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
} else if (isChanging) {
final float durationScale = mWmService.getTransitionAnimationScaleLocked();
final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
final AnimationAdapter adapter = new LocalAnimationAdapter(
new WindowChangeAnimationSpec(mSurfaceFreezer.mFreezeBounds, mTmpRect,
displayInfo, durationScale, true /* isAppAnimation */,
false /* isThumbnail */),
getSurfaceAnimationRunner());
final AnimationAdapter thumbnailAdapter = mSurfaceFreezer.mSnapshot != null
? new LocalAnimationAdapter(new WindowChangeAnimationSpec(
mSurfaceFreezer.mFreezeBounds, mTmpRect, displayInfo, durationScale,
true /* isAppAnimation */, true /* isThumbnail */), getSurfaceAnimationRunner())
: null;
resultAdapters = new Pair<>(adapter, thumbnailAdapter);
mTransit = transit;
mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
} else {
mNeedsAnimationBoundsLayer = (appRootTaskClipMode == ROOT_TASK_CLIP_AFTER_ANIM);
final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
if (a != null) {
// Only apply corner radius to animation if we're not in multi window mode.
// We don't want rounded corners when in pip or split screen.
final float windowCornerRadius = !inMultiWindowMode()
? getDisplayContent().getWindowCornerRadius()
: 0;
if (asActivityRecord() != null
&& asActivityRecord().isNeedsLetterboxedAnimation()) {
asActivityRecord().getLetterboxInnerBounds(mTmpRect);
}
AnimationAdapter adapter = new LocalAnimationAdapter(
new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
getDisplayContent().mAppTransition.canSkipFirstFrame(),
appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius),
getSurfaceAnimationRunner());
resultAdapters = new Pair<>(adapter, null);
mNeedsZBoost = a.getZAdjustment() == Animation.ZORDER_TOP
|| AppTransition.isClosingTransitOld(transit);
mTransit = transit;
mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
} else {
resultAdapters = new Pair<>(null, null);
}
}
return resultAdapters;
}
protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
@TransitionOldType int transit, boolean isVoiceInteraction,
@Nullable ArrayList<WindowContainer> sources) {
final Task task = asTask();
if (task != null && !enter && !task.isActivityTypeHomeOrRecents()) {
final InsetsControlTarget imeTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
final boolean isImeLayeringTarget = imeTarget != null && imeTarget.getWindow() != null
&& imeTarget.getWindow().getTask() == task;
// Attach and show the IME screenshot when the task is the IME target and performing
// task closing transition to the next task.
if (isImeLayeringTarget && AppTransition.isTaskCloseTransitOld(transit)) {
mDisplayContent.showImeScreenshot();
}
}
final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
transit, enter, isVoiceInteraction);
AnimationAdapter adapter = adapters.first;
AnimationAdapter thumbnailAdapter = adapters.second;
if (adapter != null) {
if (sources != null) {
mSurfaceAnimationSources.addAll(sources);
}
AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();
if (isTaskTransitOld(transit) && getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
animationRunnerBuilder.setTaskBackgroundColor(getTaskAnimationBackgroundColor());
// TODO: Remove when we migrate to shell (b/202383002)
if (mWmService.mTaskTransitionSpec != null) {
animationRunnerBuilder.hideInsetSourceViewOverflows();
}
}
// Check if the animation requests to show background color for Activity and embedded
// TaskFragment.
final ActivityRecord activityRecord = asActivityRecord();
final TaskFragment taskFragment = asTaskFragment();
if (adapter.getShowBackground()
// Check if it is Activity transition.
&& ((activityRecord != null && isActivityTransitOld(transit))
// Check if it is embedded TaskFragment transition.
|| (taskFragment != null && taskFragment.isEmbedded()
&& isTaskFragmentTransitOld(transit)))) {
final @ColorInt int backgroundColorForTransition;
if (adapter.getBackgroundColor() != 0) {
// If available use the background color provided through getBackgroundColor
// which if set originates from a call to overridePendingAppTransition.
backgroundColorForTransition = adapter.getBackgroundColor();
} else {
final TaskFragment organizedTf = activityRecord != null
? activityRecord.getOrganizedTaskFragment()
: taskFragment.getOrganizedTaskFragment();
if (organizedTf != null && organizedTf.getAnimationParams()
.getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
// This window is embedded and has an animation background color set on the
// TaskFragment. Pass this color with this window, so the handler can use it
// as the animation background color if needed,
backgroundColorForTransition = organizedTf.getAnimationParams()
.getAnimationBackgroundColor();
} else {
// Otherwise default to the window's background color if provided through
// the theme as the background color for the animation - the top most window
// with a valid background color and showBackground set takes precedence.
final Task parentTask = activityRecord != null
? activityRecord.getTask()
: taskFragment.getTask();
backgroundColorForTransition = parentTask.getTaskDescription()
.getBackgroundColor();
}
}
// Set to opaque for animation background to prevent it from exposing the blank
// background or content below.
animationRunnerBuilder.setTaskBackgroundColor(ColorUtils.setAlphaComponent(
backgroundColorForTransition, 255));
}
animationRunnerBuilder.build()
.startAnimation(getPendingTransaction(), adapter, !isVisible(),
ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
if (adapter.getShowWallpaper()) {
getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
}
}
private @ColorInt int getTaskAnimationBackgroundColor() {
Context uiContext = mDisplayContent.getDisplayPolicy().getSystemUiContext();
TaskTransitionSpec customSpec = mWmService.mTaskTransitionSpec;
@ColorInt int defaultFallbackColor = uiContext.getColor(R.color.overview_background);
if (customSpec != null && customSpec.backgroundColor != 0) {
return customSpec.backgroundColor;
}
return defaultFallbackColor;
}
final SurfaceAnimationRunner getSurfaceAnimationRunner() {
return mWmService.mSurfaceAnimationRunner;
}
private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
boolean isVoiceInteraction) {
if (AppTransitionController.isTaskViewTask(this) || (isOrganized()
// TODO(b/161711458): Clean-up when moved to shell.
&& getWindowingMode() != WINDOWING_MODE_FULLSCREEN
&& getWindowingMode() != WINDOWING_MODE_FREEFORM
&& getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW)) {
return null;
}
final DisplayContent displayContent = getDisplayContent();
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final int width = displayInfo.appWidth;
final int height = displayInfo.appHeight;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: container=%s", this);
// Determine the visible rect to calculate the thumbnail clip with
// getAnimationFrames.
final Rect frame = new Rect(0, 0, width, height);
final Rect displayFrame = new Rect(0, 0,
displayInfo.logicalWidth, displayInfo.logicalHeight);
final Rect insets = new Rect();
final Rect stableInsets = new Rect();
final Rect surfaceInsets = new Rect();
getAnimationFrames(frame, insets, stableInsets, surfaceInsets);
if (mLaunchTaskBehind) {
// Differentiate the two animations. This one which is briefly on the screen
// gets the !enter animation, and the other one which remains on the
// screen gets the enter animation. Both appear in the mOpeningApps set.
enter = false;
}
ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
"Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s "
+ "surfaceInsets=%s",
AppTransition.appTransitionOldToString(transit), enter, frame, insets,
surfaceInsets);
final Configuration displayConfig = displayContent.getConfiguration();
final Animation a = getDisplayContent().mAppTransition.loadAnimation(lp, transit, enter,
displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
surfaceInsets, stableInsets, isVoiceInteraction, inFreeformWindowingMode(), this);
if (a != null) {
if (a != null) {
// Setup the maximum app transition duration to prevent malicious app may set a long
// animation duration or infinite repeat counts for the app transition through
// ActivityOption#makeCustomAnimation or WindowManager#overridePendingTransition.
a.restrictDuration(MAX_APP_TRANSITION_DURATION);
}
if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {
ProtoLog.i(WM_DEBUG_ANIM, "Loaded animation %s for %s, duration: %d, stack=%s",
a, this, ((a != null) ? a.getDuration() : 0), Debug.getCallers(20));
}
final int containingWidth = frame.width();
final int containingHeight = frame.height();
a.initialize(containingWidth, containingHeight, width, height);
a.scaleCurrentDuration(mWmService.getTransitionAnimationScaleLocked());
}
return a;
}
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
return null;
}
boolean canCreateRemoteAnimationTarget() {
return false;
}
boolean okToDisplay() {
final DisplayContent dc = getDisplayContent();
return dc != null && dc.okToDisplay();
}
boolean okToAnimate() {
return okToAnimate(false /* ignoreFrozen */, false /* ignoreScreenOn */);
}
boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) {
final DisplayContent dc = getDisplayContent();
return dc != null && dc.okToAnimate(ignoreFrozen, ignoreScreenOn);
}
@Override
public void commitPendingTransaction() {
scheduleAnimation();
}
void transformFrameToSurfacePosition(int left, int top, Point outPoint) {
outPoint.set(left, top);
final WindowContainer parentWindowContainer = getParent();
if (parentWindowContainer == null) {
return;
}
final Rect parentBounds = parentWindowContainer.getBounds();
outPoint.offset(-parentBounds.left, -parentBounds.top);
}
void reassignLayer(Transaction t) {
final WindowContainer parent = getParent();
if (parent != null) {
parent.assignChildLayers(t);
}
}
void resetSurfacePositionForAnimationLeash(Transaction t) {
t.setPosition(mSurfaceControl, 0, 0);
final SurfaceControl.Transaction syncTransaction = getSyncTransaction();
if (t != syncTransaction) {
// Avoid restoring to old position if the sync transaction is applied later.
syncTransaction.setPosition(mSurfaceControl, 0, 0);
}
mLastSurfacePosition.set(0, 0);
}
@Override
public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
mLastLayer = -1;
mAnimationLeash = leash;
reassignLayer(t);
// Leash is now responsible for position, so set our position to 0.
resetSurfacePositionForAnimationLeash(t);
}
@Override
public void onAnimationLeashLost(Transaction t) {
mLastLayer = -1;
mWmService.mSurfaceAnimationRunner.onAnimationLeashLost(mAnimationLeash, t);
mAnimationLeash = null;
mNeedsZBoost = false;
reassignLayer(t);
updateSurfacePosition(t);
}
@Override
public SurfaceControl getAnimationLeash() {
return mAnimationLeash;
}
private void doAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
for (int i = 0; i < mSurfaceAnimationSources.size(); ++i) {
mSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim);
}
mSurfaceAnimationSources.clear();
if (mDisplayContent != null) {
mDisplayContent.onWindowAnimationFinished(this, type);
}
}
/**
* Called when an animation has finished running.
*/
protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
doAnimationFinished(type, anim);
mWmService.onAnimationFinished();
mNeedsZBoost = false;
}
/**
* @return The currently running animation, if any, or {@code null} otherwise.
*/
AnimationAdapter getAnimation() {
return mSurfaceAnimator.getAnimation();
}
/**
* @return The {@link WindowContainer} which is running an animation.
*
* By default this only checks if this container itself is actually running an animation, but
* you can extend the check target over its relatives, or relax the condition so that this can
* return {@code WindowContainer} if an animation starts soon by giving a combination
* of {@link AnimationFlags}.
*
* Note that you can give a combination of bitmask flags to specify targets and condition for
* checking animating status.
* e.g. {@code isAnimating(TRANSITION | PARENT)} returns {@code true} if either this
* container itself or one of its parents is running an animation or waiting for an app
* transition.
*
* Note that TRANSITION propagates to parents and children as well.
*
* @param flags The combination of bitmask flags to specify targets and condition for
* checking animating status.
* @param typesToCheck The combination of bitmask {@link AnimationType} to compare when
* determining if animating.
*
* @see AnimationFlags#TRANSITION
* @see AnimationFlags#PARENTS
* @see AnimationFlags#CHILDREN
*/
@Nullable
WindowContainer getAnimatingContainer(int flags, int typesToCheck) {
if (isSelfAnimating(flags, typesToCheck)) {
return this;
}
if ((flags & PARENTS) != 0) {
WindowContainer parent = getParent();
while (parent != null) {
if (parent.isSelfAnimating(flags, typesToCheck)) {
return parent;
}
parent = parent.getParent();
}
}
if ((flags & CHILDREN) != 0) {
for (int i = 0; i < mChildren.size(); ++i) {
final WindowContainer wc = mChildren.get(i).getAnimatingContainer(
flags & ~PARENTS, typesToCheck);
if (wc != null) {
return wc;
}
}
}
return null;
}
/**
* Internal method only to be used during {@link #getAnimatingContainer(int, int)}.DO NOT CALL
* FROM OUTSIDE.
*/
protected boolean isSelfAnimating(int flags, int typesToCheck) {
if (mSurfaceAnimator.isAnimating()
&& (mSurfaceAnimator.getAnimationType() & typesToCheck) > 0) {
return true;
}
if ((flags & TRANSITION) != 0 && isWaitingForTransitionStart()) {
return true;
}
return false;
}
/**
* @deprecated Use {@link #getAnimatingContainer(int, int)} instead.
*/
@Nullable
@Deprecated
final WindowContainer getAnimatingContainer() {
return getAnimatingContainer(PARENTS, ANIMATION_TYPE_ALL);
}
/**
* @see SurfaceAnimator#startDelayingAnimationStart
*/
void startDelayingAnimationStart() {
mSurfaceAnimator.startDelayingAnimationStart();
}
/**
* @see SurfaceAnimator#endDelayingAnimationStart
*/
void endDelayingAnimationStart() {
mSurfaceAnimator.endDelayingAnimationStart();
}
@Override
public int getSurfaceWidth() {
return mSurfaceControl.getWidth();
}
@Override
public int getSurfaceHeight() {
return mSurfaceControl.getHeight();
}
@CallSuper
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
if (mSurfaceAnimator.isAnimating()) {
pw.print(prefix); pw.println("ContainerAnimator:");
mSurfaceAnimator.dump(pw, prefix + " ");
}
if (mLastOrientationSource != null && this == mDisplayContent) {
pw.println(prefix + "mLastOrientationSource=" + mLastOrientationSource);
pw.println(prefix + "deepestLastOrientationSource=" + getLastOrientationSource());
}
if (mLocalInsetsSources != null && mLocalInsetsSources.size() != 0) {
pw.println(prefix + mLocalInsetsSources.size() + " LocalInsetsSources");
final String childPrefix = prefix + " ";
for (int i = 0; i < mLocalInsetsSources.size(); ++i) {
mLocalInsetsSources.valueAt(i).dump(childPrefix, pw);
}
}
}
final void updateSurfacePositionNonOrganized() {
// Avoid fighting with the organizer over Surface position.
if (isOrganized()) return;
updateSurfacePosition(getSyncTransaction());
}
/**
* Only for use internally (see PROTECTED annotation). This should only be used over
* {@link #updateSurfacePositionNonOrganized} when the surface position needs to be
* updated even if organized (eg. while changing to organized).
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
void updateSurfacePosition(Transaction t) {
if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
return;
}
if (isClosingWhenResizing()) {
// This container is closing while resizing, keep its surface at the starting position
// to prevent animation flicker.
getRelativePosition(mDisplayContent.mClosingChangingContainers.get(this), mTmpPos);
} else {
getRelativePosition(mTmpPos);
}
final int deltaRotation = getRelativeDisplayRotation();
if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) {
return;
}
t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
// set first, since we don't want rotation included in this (for now).
mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);
if (mTransitionController.isShellTransitionsEnabled()
&& !mTransitionController.useShellTransitionsRotation()) {
if (deltaRotation != Surface.ROTATION_0) {
updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
getPendingTransaction().setFixedTransformHint(mSurfaceControl,
getWindowConfiguration().getDisplayRotation());
} else if (deltaRotation != mLastDeltaRotation) {
t.setMatrix(mSurfaceControl, 1, 0, 0, 1);
getPendingTransaction().unsetFixedTransformHint(mSurfaceControl);
}
}
mLastDeltaRotation = deltaRotation;
}
/**
* Updates the surface transform based on a difference in displayed-rotation from its parent.
* @param positionLeash If non-null, the rotated position will be set on this surface instead
* of the window surface. {@see WindowToken#getOrCreateFixedRotationLeash}.
*/
protected void updateSurfaceRotation(Transaction t, @Surface.Rotation int deltaRotation,
@Nullable SurfaceControl positionLeash) {
// parent must be non-null otherwise deltaRotation would be 0.
RotationUtils.rotateSurface(t, mSurfaceControl, deltaRotation);
mTmpPos.set(mLastSurfacePosition.x, mLastSurfacePosition.y);
final Rect parentBounds = getParent().getBounds();
final boolean flipped = (deltaRotation % 2) != 0;
RotationUtils.rotatePoint(mTmpPos, deltaRotation,
flipped ? parentBounds.height() : parentBounds.width(),
flipped ? parentBounds.width() : parentBounds.height());
t.setPosition(positionLeash != null ? positionLeash : mSurfaceControl,
mTmpPos.x, mTmpPos.y);
}
@VisibleForTesting
Point getLastSurfacePosition() {
return mLastSurfacePosition;
}
/**
* The {@code outFrame} retrieved by this method specifies where the animation will finish
* the entrance animation, as the next frame will display the window at these coordinates. In
* case of exit animation, this is where the animation will start, as the frame before the
* animation is displaying the window at these bounds.
*
* @param outFrame The bounds where entrance animation finishes or exit animation starts.
* @param outInsets Insets that are covered by system windows.
* @param outStableInsets Insets that determine the area covered by the stable system windows.
* @param outSurfaceInsets Positive insets between the drawing surface and window content.
*/
void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
Rect outSurfaceInsets) {
final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
outFrame.set(0, 0, displayInfo.appWidth, displayInfo.appHeight);
outInsets.setEmpty();
outStableInsets.setEmpty();
outSurfaceInsets.setEmpty();
}
/** Gets the position of this container in its parent's coordinate. */
void getRelativePosition(Point outPos) {
getRelativePosition(getBounds(), outPos);
}
/** Gets the position of {@code curBounds} in this container's parent's coordinate. */
void getRelativePosition(Rect curBounds, Point outPos) {
outPos.set(curBounds.left, curBounds.top);
final WindowContainer parent = getParent();
if (parent != null) {
final Rect parentBounds = parent.getBounds();
outPos.offset(-parentBounds.left, -parentBounds.top);
}
}
/** @return the difference in displayed-rotation from parent. */
@Surface.Rotation
int getRelativeDisplayRotation() {
final WindowContainer parent = getParent();
if (parent == null) return Surface.ROTATION_0;
final int rotation = getWindowConfiguration().getDisplayRotation();
final int parentRotation = parent.getWindowConfiguration().getDisplayRotation();
return RotationUtils.deltaRotation(rotation, parentRotation);
}
void waitForAllWindowsDrawn() {
forAllWindows(w -> {
w.requestDrawIfNeeded(mWaitingForDrawn);
}, true /* traverseTopToBottom */);
}
Dimmer getDimmer() {
if (mParent == null) {
return null;
}
return mParent.getDimmer();
}
void setSurfaceControl(SurfaceControl sc) {
mSurfaceControl = sc;
}
RemoteAnimationDefinition getRemoteAnimationDefinition() {
return null;
}
/** Cheap way of doing cast and instanceof. */
Task asTask() {
return null;
}
/** Cheap way of doing cast and instanceof. */
TaskFragment asTaskFragment() {
return null;
}
/** Cheap way of doing cast and instanceof. */
WindowToken asWindowToken() {
return null;
}
/** Cheap way of doing cast and instanceof. */
WindowState asWindowState() {
return null;
}
/** Cheap way of doing cast and instanceof. */
ActivityRecord asActivityRecord() {
return null;
}
/** Cheap way of doing cast and instanceof. */
WallpaperWindowToken asWallpaperToken() {
return null;
}
/** Cheap way of doing cast and instanceof. */
DisplayArea asDisplayArea() {
return null;
}
/** Cheap way of doing cast and instanceof. */
RootDisplayArea asRootDisplayArea() {
return null;
}
/** Cheap way of doing cast and instanceof. */
TaskDisplayArea asTaskDisplayArea() {
return null;
}
/** Cheap way of doing cast and instanceof. */
DisplayContent asDisplayContent() {
return null;
}
/**
* @return {@code true} if window container is manage by a
* {@link android.window.WindowOrganizer}
*/
boolean isOrganized() {
return false;
}
/** @return {@code true} if this is a container for embedded activities or tasks. */
boolean isEmbedded() {
return false;
}
/**
* @return {@code true} if this container's surface should be shown when it is created.
*/
boolean showSurfaceOnCreation() {
return true;
}
/** @return {@code true} if the wallpaper is visible behind this container. */
boolean showWallpaper() {
if (!isVisibleRequested()
// in multi-window mode, wallpaper is always visible at the back and not tied to
// the app (there is no wallpaper target).
|| inMultiWindowMode()) {
return false;
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mChildren.get(i);
if (child.showWallpaper()) {
return true;
}
}
return false;
}
@Nullable
static WindowContainer fromBinder(IBinder binder) {
return RemoteToken.fromBinder(binder).getContainer();
}
static class RemoteToken extends IWindowContainerToken.Stub {
final WeakReference<WindowContainer> mWeakRef;
private WindowContainerToken mWindowContainerToken;
RemoteToken(WindowContainer container) {
mWeakRef = new WeakReference<>(container);
}
@Nullable
WindowContainer getContainer() {
return mWeakRef.get();
}
static RemoteToken fromBinder(IBinder binder) {
return (RemoteToken) binder;
}
WindowContainerToken toWindowContainerToken() {
if (mWindowContainerToken == null) {
mWindowContainerToken = new WindowContainerToken(this);
}
return mWindowContainerToken;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("RemoteToken{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(' ');
sb.append(mWeakRef.get());
sb.append('}');
return sb.toString();
}
}
/**
* Call this when this container finishes drawing content.
*
* @return {@code true} if consumed (this container is part of a sync group).
*/
boolean onSyncFinishedDrawing() {
if (mSyncState == SYNC_STATE_NONE) return false;
mSyncState = SYNC_STATE_READY;
mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "onSyncFinishedDrawing %s", this);
return true;
}
void setSyncGroup(@NonNull BLASTSyncEngine.SyncGroup group) {
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "setSyncGroup #%d on %s", group.mSyncId, this);
if (mSyncGroup != null && mSyncGroup != group) {
// This can still happen if WMCore starts a new transition when there is ongoing
// sync transaction from Shell. Please file a bug if it happens.
throw new IllegalStateException("Can't sync on 2 groups simultaneously"
+ " currentSyncId=" + mSyncGroup.mSyncId + " newSyncId=" + group.mSyncId
+ " wc=" + this);
}
mSyncGroup = group;
}
@Nullable
BLASTSyncEngine.SyncGroup getSyncGroup() {
if (mSyncGroup != null) return mSyncGroup;
if (mParent != null) return mParent.getSyncGroup();
return null;
}
/**
* Prepares this container for participation in a sync-group. This includes preparing all its
* children.
*
* @return {@code true} if something changed (eg. this wasn't already in the sync group).
*/
boolean prepareSync() {
if (mSyncState != SYNC_STATE_NONE) {
// Already part of sync
return false;
}
for (int i = getChildCount() - 1; i >= 0; --i) {
final WindowContainer child = getChildAt(i);
child.prepareSync();
}
mSyncState = SYNC_STATE_READY;
return true;
}
boolean syncNextBuffer() {
return mSyncState != SYNC_STATE_NONE;
}
/**
* Recursively finishes/cleans-up sync state of this subtree and collects all the sync
* transactions into `outMergedTransaction`.
* @param outMergedTransaction A transaction to merge all the recorded sync operations into.
* @param cancel If true, this is being finished because it is leaving the sync group rather
* than due to the sync group completing.
*/
void finishSync(Transaction outMergedTransaction, BLASTSyncEngine.SyncGroup group,
boolean cancel) {
if (mSyncState == SYNC_STATE_NONE) return;
final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
// If it's null, then we need to clean-up anyways.
if (syncGroup != null && group != syncGroup) return;
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "finishSync cancel=%b for %s", cancel, this);
outMergedTransaction.merge(mSyncTransaction);
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).finishSync(outMergedTransaction, group, cancel);
}
if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this);
mSyncState = SYNC_STATE_NONE;
mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
mSyncGroup = null;
}
/**
* Checks if the subtree rooted at this container is finished syncing (everything is ready or
* not visible). NOTE, this is not const: it may cancel/prepare/complete itself depending on
* its state in the hierarchy.
*
* @return {@code true} if this subtree is finished waiting for sync participants.
*/
boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
if (!isVisibleRequested()) {
return true;
}
if (mSyncState == SYNC_STATE_NONE) {
prepareSync();
}
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW) {
return false;
}
// READY
// Loop from top-down.
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mChildren.get(i);
final boolean childFinished = group.isIgnoring(child) || child.isSyncFinished(group);
if (childFinished && child.isVisibleRequested() && child.fillsParent()) {
// Any lower children will be covered-up, so we can consider this finished.
return true;
}
if (!childFinished) {
return false;
}
}
return true;
}
/**
* Special helper to check that all windows are synced (vs just top one). This is only
* used to differentiate between starting-window vs full-drawn in activity-metrics reporting.
*/
boolean allSyncFinished() {
if (!isVisibleRequested()) return true;
if (mSyncState != SYNC_STATE_READY) return false;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mChildren.get(i);
if (!child.allSyncFinished()) return false;
}
return true;
}
/**
* Called during reparent to handle sync state when the hierarchy changes.
* If this is in a sync group and gets reparented out, it will cancel syncing.
* If this is not in a sync group and gets parented into one, it will prepare itself.
* If its moving around within a sync-group, it needs to restart its syncing since a
* hierarchy change implies a configuration change.
*/
private void onSyncReparent(WindowContainer oldParent, WindowContainer newParent) {
// Check if this is changing displays. If so, mark the old display as "ready" for
// transitions. This is to work around the problem where setting readiness against this
// container will only set the new display as ready and leave the old display as unready.
if (mSyncState != SYNC_STATE_NONE && oldParent != null && newParent != null
&& oldParent.getDisplayContent() != null && newParent.getDisplayContent() != null
&& oldParent.getDisplayContent() != newParent.getDisplayContent()) {
mTransitionController.setReady(oldParent.getDisplayContent());
}
if (newParent == null || newParent.mSyncState == SYNC_STATE_NONE) {
if (mSyncState == SYNC_STATE_NONE) {
return;
}
if (newParent == null) {
// This is getting removed.
if (oldParent.mSyncState != SYNC_STATE_NONE) {
// In order to keep the transaction in sync, merge it into the parent.
finishSync(oldParent.mSyncTransaction, getSyncGroup(), true /* cancel */);
} else if (mSyncGroup != null) {
// This is watched directly by the sync-group, so merge this transaction into
// into the sync-group so it isn't lost
finishSync(mSyncGroup.getOrphanTransaction(), mSyncGroup, true /* cancel */);
} else {
throw new IllegalStateException("This container is in sync mode without a sync"
+ " group: " + this);
}
return;
} else if (mSyncGroup == null) {
// This is being reparented out of the sync-group. To prevent ordering issues on
// this container, immediately apply/cancel sync on it.
finishSync(getPendingTransaction(), getSyncGroup(), true /* cancel */);
return;
}
// Otherwise this is the "root" of a synced subtree, so continue on to preparation.
}
if (oldParent != null && newParent != null && !shouldUpdateSyncOnReparent()) {
return;
}
// This container's situation has changed so we need to restart its sync.
// We cannot reset the sync without a chance of a deadlock since it will request a new
// buffer from the app process. This could cause issues if the app has run out of buffers
// since the previous buffer was already synced and is still held in a transaction.
// Resetting syncState violates the policies outlined in BlastSyncEngine.md so for now
// disable this when shell transitions is disabled.
if (mTransitionController.isShellTransitionsEnabled()) {
mSyncState = SYNC_STATE_NONE;
mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
}
prepareSync();
}
/** Returns {@code true} if {@link #mSyncState} needs to be updated when reparenting. */
protected boolean shouldUpdateSyncOnReparent() {
return true;
}
void registerWindowContainerListener(WindowContainerListener listener) {
registerWindowContainerListener(listener, true /* shouldPropConfig */);
}
void registerWindowContainerListener(WindowContainerListener listener,
boolean shouldDispatchConfig) {
if (mListeners.contains(listener)) {
return;
}
mListeners.add(listener);
// Also register to ConfigurationChangeListener to receive configuration changes.
registerConfigurationChangeListener(listener, shouldDispatchConfig);
if (shouldDispatchConfig) {
listener.onDisplayChanged(getDisplayContent());
}
}
void unregisterWindowContainerListener(WindowContainerListener listener) {
mListeners.remove(listener);
unregisterConfigurationChangeListener(listener);
}
static void overrideConfigurationPropagation(WindowContainer<?> receiver,
WindowContainer<?> supplier) {
overrideConfigurationPropagation(receiver, supplier, null /* configurationMerger */);
}
/**
* Forces the receiver container to always use the configuration of the supplier container as
* its requested override configuration. It allows to propagate configuration without changing
* the relationship between child and parent.
*
* @param receiver The {@link WindowContainer<?>} which will receive the {@link
* Configuration} result of the merging operation.
* @param supplier The {@link WindowContainer<?>} which provides the initial {@link
* Configuration}.
* @param configurationMerger A {@link ConfigurationMerger} which combines the {@link
* Configuration} of the receiver and the supplier.
*/
static WindowContainerListener overrideConfigurationPropagation(WindowContainer<?> receiver,
WindowContainer<?> supplier, @Nullable ConfigurationMerger configurationMerger) {
final ConfigurationContainerListener listener = new ConfigurationContainerListener() {
@Override
public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
final Configuration mergedConfiguration =
configurationMerger != null
? configurationMerger.merge(mergedOverrideConfig,
receiver.getRequestedOverrideConfiguration())
: supplier.getConfiguration();
receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration);
}
};
supplier.registerConfigurationChangeListener(listener);
final WindowContainerListener wcListener = new WindowContainerListener() {
@Override
public void onRemoved() {
receiver.unregisterWindowContainerListener(this);
supplier.unregisterConfigurationChangeListener(listener);
}
};
receiver.registerWindowContainerListener(wcListener);
return wcListener;
}
/**
* Abstraction for functions merging two {@link Configuration} objects into one.
*/
@FunctionalInterface
interface ConfigurationMerger {
Configuration merge(Configuration first, Configuration second);
}
/**
* Returns the {@link WindowManager.LayoutParams.WindowType}.
*/
@WindowManager.LayoutParams.WindowType int getWindowType() {
return INVALID_WINDOW_TYPE;
}
boolean setCanScreenshot(Transaction t, boolean canScreenshot) {
if (mSurfaceControl == null) {
return false;
}
t.setSecure(mSurfaceControl, !canScreenshot);
return true;
}
private class AnimationRunnerBuilder {
/**
* Runs when the surface stops animating
*/
private final List<Runnable> mOnAnimationFinished = new LinkedList<>();
/**
* Runs when the animation is cancelled but the surface is still animating
*/
private final List<Runnable> mOnAnimationCancelled = new LinkedList<>();
private void setTaskBackgroundColor(@ColorInt int backgroundColor) {
TaskDisplayArea taskDisplayArea = getTaskDisplayArea();
if (taskDisplayArea != null && backgroundColor != Color.TRANSPARENT) {
taskDisplayArea.setBackgroundColor(backgroundColor);
// Atomic counter to make sure the clearColor callback is only called one.
// It will be called twice in the case we cancel the animation without restart
// (in that case it will run as the cancel and finished callbacks).
final AtomicInteger callbackCounter = new AtomicInteger(0);
final Runnable clearBackgroundColorHandler = () -> {
if (callbackCounter.getAndIncrement() == 0) {
taskDisplayArea.clearBackgroundColor();
}
};
// We want to make sure this is called both when the surface stops animating and
// also when an animation is cancelled (i.e. animation is replaced by another
// animation but and so the surface is still animating)
mOnAnimationFinished.add(clearBackgroundColorHandler);
mOnAnimationCancelled.add(clearBackgroundColorHandler);
}
}
private void hideInsetSourceViewOverflows() {
final SparseArray<InsetsSourceProvider> providers =
getDisplayContent().getInsetsStateController().getSourceProviders();
for (int i = providers.size(); i >= 0; i--) {
final InsetsSourceProvider insetProvider = providers.valueAt(i);
if (!insetProvider.getSource().hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
return;
}
// Will apply it immediately to current leash and to all future inset animations
// until we disable it.
insetProvider.setCropToProvidingInsetsBounds(getPendingTransaction());
// Only clear the size restriction of the inset once the surface animation is over
// and not if it's canceled to be replace by another animation.
mOnAnimationFinished.add(() -> {
insetProvider.removeCropToProvidingInsetsBounds(getPendingTransaction());
});
}
}
private IAnimationStarter build() {
return (Transaction t, AnimationAdapter adapter, boolean hidden,
@AnimationType int type, @Nullable AnimationAdapter snapshotAnim) -> {
startAnimation(getPendingTransaction(), adapter, !isVisible(), type,
(animType, anim) -> mOnAnimationFinished.forEach(Runnable::run),
() -> mOnAnimationCancelled.forEach(Runnable::run), snapshotAnim);
};
}
}
private interface IAnimationStarter {
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type, @Nullable AnimationAdapter snapshotAnim);
}
void addTrustedOverlay(SurfaceControlViewHost.SurfacePackage overlay,
@Nullable WindowState initialWindowState) {
if (mOverlayHost == null) {
mOverlayHost = new TrustedOverlayHost(mWmService);
}
mOverlayHost.addOverlay(overlay, mSurfaceControl);
// Emit an initial onConfigurationChanged to ensure the overlay
// can receive any changes between their creation time and
// attach time.
try {
overlay.getRemoteInterface().onConfigurationChanged(getConfiguration());
} catch (Exception e) {
ProtoLog.e(WM_DEBUG_ANIM,
"Error sending initial configuration change to WindowContainer overlay");
removeTrustedOverlay(overlay);
}
// Emit an initial WindowState so that proper insets are available to overlay views
// shortly after the overlay is added.
if (initialWindowState != null) {
final InsetsState insetsState = initialWindowState.getInsetsState();
final Rect dispBounds = getBounds();
try {
overlay.getRemoteInterface().onInsetsChanged(insetsState, dispBounds);
} catch (Exception e) {
ProtoLog.e(WM_DEBUG_ANIM,
"Error sending initial insets change to WindowContainer overlay");
removeTrustedOverlay(overlay);
}
}
}
void removeTrustedOverlay(SurfaceControlViewHost.SurfacePackage overlay) {
if (mOverlayHost != null && !mOverlayHost.removeOverlay(overlay)) {
mOverlayHost.release();
mOverlayHost = null;
}
}
void updateOverlayInsetsState(WindowState originalChange) {
final WindowContainer p = getParent();
if (p != null) {
p.updateOverlayInsetsState(originalChange);
}
}
void waitForSyncTransactionCommit(ArraySet<WindowContainer> wcAwaitingCommit) {
if (wcAwaitingCommit.contains(this)) {
return;
}
mSyncTransactionCommitCallbackDepth++;
wcAwaitingCommit.add(this);
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).waitForSyncTransactionCommit(wcAwaitingCommit);
}
}
void onSyncTransactionCommitted(SurfaceControl.Transaction t) {
mSyncTransactionCommitCallbackDepth--;
if (mSyncTransactionCommitCallbackDepth > 0) {
return;
}
if (mSyncState != SYNC_STATE_NONE) {
return;
}
t.merge(mSyncTransaction);
}
}