| /* |
| * 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.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.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.view.SurfaceControl.Transaction; |
| |
| import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; |
| import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; |
| import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; |
| import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; |
| import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; |
| 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.WindowContainerProto.CONFIGURATION_CONTAINER; |
| import static com.android.server.wm.WindowContainerProto.ORIENTATION; |
| import static com.android.server.wm.WindowContainerProto.SURFACE_ANIMATOR; |
| import static com.android.server.wm.WindowContainerProto.VISIBLE; |
| import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; |
| 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.WindowManagerService.logWithStack; |
| import static com.android.server.wm.WindowManagerService.sHierarchicalAnimations; |
| import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; |
| |
| import android.annotation.CallSuper; |
| import android.annotation.IntDef; |
| import android.annotation.Nullable; |
| import android.app.WindowConfiguration; |
| import android.content.pm.ActivityInfo; |
| import android.content.res.Configuration; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.os.Debug; |
| import android.os.IBinder; |
| import android.os.Trace; |
| import android.util.Pair; |
| import android.util.Pools; |
| import android.util.Slog; |
| import android.util.proto.ProtoOutputStream; |
| import android.view.DisplayInfo; |
| import android.view.IWindowContainer; |
| import android.view.MagnificationSpec; |
| import android.view.RemoteAnimationTarget; |
| import android.view.SurfaceControl; |
| import android.view.SurfaceControl.Builder; |
| import android.view.SurfaceSession; |
| import android.view.WindowManager; |
| import android.view.animation.Animation; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.ToBooleanFunction; |
| import com.android.server.protolog.common.ProtoLog; |
| 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.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 { |
| |
| private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM; |
| |
| /** Animation layer that happens above all animating {@link ActivityStack}s. */ |
| static final int ANIMATION_LAYER_STANDARD = 0; |
| |
| /** Animation layer that happens above all {@link ActivityStack}s. */ |
| static final int ANIMATION_LAYER_BOOSTED = 1; |
| |
| /** |
| * Animation layer that is reserved for {@link WindowConfiguration#ACTIVITY_TYPE_HOME} |
| * activities and all activities that are being controlled by the recents animation. This |
| * layer is generally below all {@link ActivityStack}s. |
| */ |
| static final int ANIMATION_LAYER_HOME = 2; |
| |
| @IntDef(prefix = { "ANIMATION_LAYER_" }, value = { |
| ANIMATION_LAYER_STANDARD, |
| ANIMATION_LAYER_BOOSTED, |
| ANIMATION_LAYER_HOME, |
| }) |
| @interface AnimationLayer {} |
| |
| 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; |
| |
| // 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. |
| @ActivityInfo.ScreenOrientation |
| protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED; |
| |
| 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; |
| protected final WindowManagerService mWmService; |
| |
| private final Point mTmpPos = new Point(); |
| protected final Point mLastSurfacePosition = new Point(); |
| |
| /** 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 final Configuration mTmpConfig = new Configuration(); |
| |
| /** 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; |
| } |
| |
| /** |
| * Callback which is triggered while changing the parent, after setting up the surface but |
| * before asking the parent to assign child layers. |
| */ |
| interface PreAssignChildLayersCallback { |
| void onPreAssignChildLayers(); |
| } |
| |
| /** |
| * 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. Must be one of |
| * {@link AppTransition#TransitionFlags}. |
| */ |
| 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 Rect mTransitStartRect = new Rect(); |
| final Point mTmpPoint = new Point(); |
| protected final Rect mTmpRect = new Rect(); |
| final Rect mTmpPrevBounds = new Rect(); |
| |
| private MagnificationSpec mLastMagnificationSpec; |
| |
| private boolean mIsFocusable = true; |
| |
| /** |
| * Used as a unique, cross-process identifier for this Container. It also serves a minimal |
| * interface to other processes. |
| */ |
| RemoteToken mRemoteToken = null; |
| |
| WindowContainer(WindowManagerService wms) { |
| mWmService = wms; |
| mPendingTransaction = wms.mTransactionFactory.get(); |
| mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms); |
| } |
| |
| @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); |
| updateSurfacePosition(); |
| scheduleAnimation(); |
| } |
| |
| void reparent(WindowContainer newParent, int position) { |
| if (newParent == null) { |
| throw new IllegalArgumentException("reparent: can't reparent to null " + this); |
| } |
| |
| final WindowContainer oldParent = mParent; |
| if (mParent == newParent) { |
| throw new IllegalArgumentException("WC=" + this + " already child of " + mParent); |
| } |
| |
| // 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(); |
| } |
| getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); |
| |
| // Send onParentChanged notification here is we disabled sending it in setParent for |
| // reparenting case. |
| onParentChanged(newParent, oldParent); |
| } |
| |
| final protected void setParent(WindowContainer<WindowContainer> parent) { |
| final WindowContainer oldParent = mParent; |
| mParent = parent; |
| |
| if (mParent != null) { |
| mParent.onChildAdded(this); |
| } |
| if (!mReparenting) { |
| 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) { |
| onParentChanged(newParent, oldParent, null); |
| } |
| |
| void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent, |
| PreAssignChildLayersCallback callback) { |
| 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. |
| setSurfaceControl(makeSurface().build()); |
| getPendingTransaction().show(mSurfaceControl); |
| updateSurfacePosition(); |
| } 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(getPendingTransaction(), mParent.mSurfaceControl); |
| } |
| |
| if (callback != null) { |
| callback.onPreAssignChildLayers(); |
| } |
| |
| // Either way we need to ask the parent to assign us a Z-order. |
| mParent.assignChildLayers(); |
| 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(); |
| } |
| 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(); |
| } |
| 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() { |
| 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) { |
| getPendingTransaction().remove(mSurfaceControl); |
| |
| // Merge to parent transaction to ensure the transactions on this WindowContainer are |
| // applied in native even if WindowContainer is removed. |
| if (mParent != null) { |
| mParent.getPendingTransaction().merge(getPendingTransaction()); |
| } |
| |
| setSurfaceControl(null); |
| mLastSurfacePosition.set(0, 0); |
| scheduleAnimation(); |
| } |
| |
| if (mParent != null) { |
| mParent.removeChild(this); |
| } |
| } |
| |
| /** |
| * @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("removeChild: 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. |
| 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) { |
| mDisplayContent = dc; |
| if (dc != null && dc != this) { |
| dc.getPendingTransaction().merge(mPendingTransaction); |
| } |
| for (int i = mChildren.size() - 1; i >= 0; --i) { |
| final WindowContainer child = mChildren.get(i); |
| child.onDisplayChanged(dc); |
| } |
| } |
| |
| DisplayContent getDisplayContent() { |
| return mDisplayContent; |
| } |
| |
| void setWaitingForDrawnIfResizingChanged() { |
| for (int i = mChildren.size() - 1; i >= 0; --i) { |
| final WindowContainer wc = mChildren.get(i); |
| wc.setWaitingForDrawnIfResizingChanged(); |
| } |
| } |
| |
| void onResize() { |
| 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() { |
| 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(); |
| } |
| } |
| |
| void forceWindowsScaleableInTransaction(boolean force) { |
| for (int i = mChildren.size() - 1; i >= 0; --i) { |
| final WindowContainer wc = mChildren.get(i); |
| wc.forceWindowsScaleableInTransaction(force); |
| } |
| } |
| |
| /** |
| * @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. |
| * |
| * {@see AnimationFlags#TRANSITION} |
| * {@see AnimationFlags#PARENTS} |
| * {@see AnimationFlags#CHILDREN} |
| */ |
| boolean isAnimating(int flags) { |
| if (mSurfaceAnimator.isAnimating()) { |
| return true; |
| } |
| if ((flags & TRANSITION) != 0 && isWaitingForTransitionStart()) { |
| return true; |
| } |
| if ((flags & PARENTS) != 0) { |
| final WindowContainer parent = getParent(); |
| if (parent != null && parent.isAnimating(flags & ~CHILDREN)) { |
| return true; |
| } |
| } |
| if ((flags & CHILDREN) != 0) { |
| for (int i = 0; i < mChildren.size(); ++i) { |
| final WindowContainer wc = mChildren.get(i); |
| if (wc.isAnimating(flags & ~PARENTS)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * @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 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; |
| } |
| |
| /** |
| * 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() { |
| return getParent().getTopChild() == this && getParent().isOnTop(); |
| } |
| |
| /** Returns the top child container. */ |
| E getTopChild() { |
| return mChildren.peekLast(); |
| } |
| |
| /** Returns true if there is still a removal being deferred */ |
| boolean checkCompleteDeferredRemoval() { |
| boolean stillDeferringRemoval = false; |
| |
| for (int i = mChildren.size() - 1; i >= 0; --i) { |
| final WindowContainer wc = mChildren.get(i); |
| stillDeferringRemoval |= wc.checkCompleteDeferredRemoval(); |
| } |
| |
| 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() { |
| 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 freezeDisplayToken freeze this app window token if display needs to freeze |
| * @param requestingContainer the container which orientation request has changed |
| * @return {@code true} if handled; {@code false} otherwise. |
| */ |
| boolean onDescendantOrientationChanged(@Nullable IBinder freezeDisplayToken, |
| @Nullable ConfigurationContainer requestingContainer) { |
| final WindowContainer parent = getParent(); |
| if (parent == null) { |
| return false; |
| } |
| return parent.onDescendantOrientationChanged(freezeDisplayToken, |
| requestingContainer); |
| } |
| |
| /** |
| * Check if this container or its parent will handle orientation changes from descendants. It's |
| * different from the return value of {@link #onDescendantOrientationChanged(IBinder, |
| * ConfigurationContainer)} 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() { |
| final WindowContainer parent = getParent(); |
| return parent != null && parent.handlesOrientationChangeFromDescendant(); |
| } |
| |
| /** |
| * Get the configuration orientation by the requested screen orientation |
| * ({@link ActivityInfo.ScreenOrientation}) of this activity. |
| * |
| * @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE}, |
| * {@link Configuration#ORIENTATION_PORTRAIT}, |
| * {@link Configuration#ORIENTATION_UNDEFINED}). |
| */ |
| int getRequestedConfigurationOrientation() { |
| if (mOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) { |
| // NOSENSOR means the display's "natural" orientation, so return that. |
| if (mDisplayContent != null) { |
| return mDisplayContent.getNaturalOrientation(); |
| } |
| } else if (mOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) { |
| // LOCKED means the activity's orientation remains unchanged, so return existing value. |
| return getConfiguration().orientation; |
| } else if (isFixedOrientationLandscape(mOrientation)) { |
| return ORIENTATION_LANDSCAPE; |
| } else if (isFixedOrientationPortrait(mOrientation)) { |
| return ORIENTATION_PORTRAIT; |
| } |
| return ORIENTATION_UNDEFINED; |
| } |
| |
| /** |
| * Calls {@link #setOrientation(int, IBinder, ActivityRecord)} with {@code null} to the last 2 |
| * parameters. |
| * |
| * @param orientation the specified orientation. |
| */ |
| void setOrientation(int orientation) { |
| setOrientation(orientation, null /* freezeDisplayToken */, |
| null /* ActivityRecord */); |
| } |
| |
| /** |
| * 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 |
| * android.content.pm.ActivityInfo.ScreenOrientation}. |
| * @param freezeDisplayToken uses this token to freeze display if orientation change is not |
| * done. Display will not be frozen if this is {@code null}, which |
| * should only happen in tests. |
| * @param requestingContainer the container which orientation request has changed. Mostly used |
| * to ensure it gets correct configuration. |
| */ |
| void setOrientation(int orientation, @Nullable IBinder freezeDisplayToken, |
| @Nullable ConfigurationContainer requestingContainer) { |
| if (mOrientation == orientation) { |
| return; |
| } |
| |
| mOrientation = orientation; |
| final int configOrientation = getRequestedConfigurationOrientation(); |
| if (getRequestedOverrideConfiguration().orientation != configOrientation) { |
| mTmpConfig.setTo(getRequestedOverrideConfiguration()); |
| mTmpConfig.orientation = configOrientation; |
| onRequestedOverrideConfigurationChanged(mTmpConfig); |
| } |
| |
| final WindowContainer parent = getParent(); |
| if (parent != null) { |
| onDescendantOrientationChanged(freezeDisplayToken, requestingContainer); |
| } |
| } |
| |
| @ActivityInfo.ScreenOrientation |
| int getOrientation() { |
| return getOrientation(mOrientation); |
| } |
| |
| /** |
| * 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. |
| */ |
| int getOrientation(int candidate) { |
| if (!fillsParent()) { |
| // Ignore containers that don't completely fill their parents. |
| 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 (mOrientation != SCREEN_ORIENTATION_UNSET |
| && mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { |
| return mOrientation; |
| } |
| |
| for (int i = mChildren.size() - 1; i >= 0; --i) { |
| final WindowContainer wc = mChildren.get(i); |
| |
| // TODO: Maybe mOrientation 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; |
| continue; |
| } |
| |
| if (orientation == SCREEN_ORIENTATION_UNSET) { |
| continue; |
| } |
| |
| if (wc.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) { |
| // Use the orientation if the container fills its parent 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)); |
| return orientation; |
| } |
| } |
| |
| return candidate; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| // 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); |
| } |
| } |
| |
| boolean showToCurrentUser() { |
| return true; |
| } |
| |
| /** |
| * 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(Function<ActivityRecord, Boolean> callback) { |
| return forAllActivities(callback, true /*traverseTopToBottom*/); |
| } |
| |
| boolean forAllActivities( |
| Function<ActivityRecord, Boolean> 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(Function<ActivityRecord, Boolean> callback, |
| WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom) { |
| return forAllActivities( |
| callback, boundary, includeBoundary, traverseTopToBottom, new boolean[1]); |
| } |
| |
| private boolean forAllActivities(Function<ActivityRecord, Boolean> 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(Function<ActivityRecord, Boolean> 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, |
| WindowContainer boundary) { |
| if (traverseTopToBottom) { |
| for (int i = mChildren.size() - 1; i >= 0; --i) { |
| final WindowContainer wc = mChildren.get(i); |
| if (wc == boundary) return null; |
| |
| 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); |
| if (wc == boundary) return null; |
| |
| 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; |
| } |
| |
| 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(Function<Task, Boolean> callback) { |
| for (int i = mChildren.size() - 1; i >= 0; --i) { |
| if (mChildren.get(i).forAllTasks(callback)) { |
| return true; |
| } |
| } |
| 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); |
| } |
| } |
| } |
| |
| 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; |
| } |
| |
| 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); |
| } |
| |
| 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; |
| } |
| |
| /** |
| * 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); |
| } |
| |
| SurfaceControl.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). |
| */ |
| SurfaceControl.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) { |
| 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) { |
| final boolean changed = layer != mLastLayer || mLastRelativeToLayer != relativeTo; |
| if (mSurfaceControl != null && changed) { |
| setRelativeLayer(t, relativeTo, layer); |
| mLastLayer = layer; |
| mLastRelativeToLayer = relativeTo; |
| } |
| } |
| |
| protected void setLayer(Transaction t, int layer) { |
| |
| // 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); |
| } |
| |
| protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { |
| |
| // 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) { |
| mSurfaceAnimator.reparent(t, 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++); |
| } |
| } |
| } |
| |
| void assignChildLayers() { |
| assignChildLayers(getPendingTransaction()); |
| scheduleAnimation(); |
| } |
| |
| boolean needsZBoost() { |
| 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, mOrientation); |
| proto.write(VISIBLE, isVisible); |
| if (mSurfaceAnimator.isAnimating()) { |
| mSurfaceAnimator.dumpDebug(proto, SURFACE_ANIMATOR); |
| } |
| proto.end(token); |
| } |
| |
| 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, spec.offsetY); |
| 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, 0, 0); |
| } |
| 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() { |
| if (mParent != null) { |
| mParent.scheduleAnimation(); |
| } |
| } |
| |
| /** |
| * @return The SurfaceControl for this container. |
| * The SurfaceControl must be valid if non-null. |
| */ |
| @Override |
| public SurfaceControl getSurfaceControl() { |
| return mSurfaceControl; |
| } |
| |
| @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. |
| */ |
| void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, |
| @AnimationType int type, |
| @Nullable OnAnimationFinishedCallback animationFinishedCallback) { |
| if (DEBUG_ANIM) { |
| Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + 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); |
| } |
| |
| 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() { |
| mSurfaceAnimator.cancelAnimation(); |
| } |
| |
| @Override |
| public Builder makeAnimationLeash() { |
| return makeSurface(); |
| } |
| |
| @Override |
| public SurfaceControl getAnimationLeashParent() { |
| return getParentSurfaceControl(); |
| } |
| |
| /** |
| * @return The layer on which all app animations are happening. |
| */ |
| SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) { |
| final WindowContainer parent = getParent(); |
| if (parent != null) { |
| return parent.getAppAnimationLayer(animationLayer); |
| } |
| return null; |
| } |
| |
| // TODO: Remove this and use #getBounds() instead once we set an app transition animation |
| // on TaskStack. |
| Rect getAnimationBounds(int appStackClipMode) { |
| return getDisplayedBounds(); |
| } |
| |
| /** |
| * 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. |
| * |
| * @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, int transit, boolean enter, |
| boolean isVoiceInteraction, |
| @Nullable OnAnimationFinishedCallback animationFinishedCallback) { |
| 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()) { |
| final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, |
| transit, enter, isVoiceInteraction); |
| AnimationAdapter adapter = adapters.first; |
| AnimationAdapter thumbnailAdapter = adapters.second; |
| if (adapter != null) { |
| startAnimation(getPendingTransaction(), adapter, !isVisible(), |
| ANIMATION_TYPE_APP_TRANSITION, animationFinishedCallback); |
| if (adapter.getShowWallpaper()) { |
| getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; |
| } |
| if (thumbnailAdapter != null) { |
| mThumbnail.startAnimation( |
| getPendingTransaction(), thumbnailAdapter, !isVisible()); |
| } |
| } |
| } 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, |
| int transit, boolean enter, boolean isVoiceInteraction) { |
| final Pair<AnimationAdapter, AnimationAdapter> resultAdapters; |
| final int appStackClipMode = getDisplayContent().mAppTransition.getAppStackClipMode(); |
| |
| // Separate position and size for use in animators. |
| mTmpRect.set(getAnimationBounds(appStackClipMode)); |
| if (sHierarchicalAnimations) { |
| getRelativeDisplayedPosition(mTmpPoint); |
| } else { |
| mTmpPoint.set(mTmpRect.left, mTmpRect.top); |
| } |
| mTmpRect.offsetTo(0, 0); |
| |
| final RemoteAnimationController controller = |
| getDisplayContent().mAppTransition.getRemoteAnimationController(); |
| final boolean isChanging = AppTransition.isChangeTransit(transit) && enter |
| && isChangingAppTransition(); |
| |
| // Delaying animation start isn't compatible with remote animations at all. |
| if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) { |
| final RemoteAnimationController.RemoteAnimationRecord adapters = |
| controller.createRemoteAnimationRecord(this, mTmpPoint, mTmpRect, |
| (isChanging ? mTransitStartRect : null)); |
| 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(mTransitStartRect, mTmpRect, displayInfo, |
| durationScale, true /* isAppAnimation */, false /* isThumbnail */), |
| getSurfaceAnimationRunner()); |
| |
| final AnimationAdapter thumbnailAdapter = mThumbnail != null |
| ? new LocalAnimationAdapter(new WindowChangeAnimationSpec(mTransitStartRect, |
| mTmpRect, displayInfo, durationScale, true /* isAppAnimation */, |
| true /* isThumbnail */), getSurfaceAnimationRunner()) |
| : null; |
| resultAdapters = new Pair<>(adapter, thumbnailAdapter); |
| mTransit = transit; |
| mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags(); |
| } else { |
| mNeedsAnimationBoundsLayer = (appStackClipMode == STACK_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; |
| AnimationAdapter adapter = new LocalAnimationAdapter( |
| new WindowAnimationSpec(a, mTmpPoint, mTmpRect, |
| getDisplayContent().mAppTransition.canSkipFirstFrame(), |
| appStackClipMode, true /* isAppAnimation */, windowCornerRadius), |
| getSurfaceAnimationRunner()); |
| |
| resultAdapters = new Pair<>(adapter, null); |
| mNeedsZBoost = a.getZAdjustment() == Animation.ZORDER_TOP; |
| mTransit = transit; |
| mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags(); |
| } else { |
| resultAdapters = new Pair<>(null, null); |
| } |
| } |
| return resultAdapters; |
| } |
| |
| final SurfaceAnimationRunner getSurfaceAnimationRunner() { |
| return mWmService.mSurfaceAnimationRunner; |
| } |
| |
| private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, |
| boolean isVoiceInteraction) { |
| 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.appTransitionToString(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 (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this); |
| 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 okToDisplay() { |
| final DisplayContent dc = getDisplayContent(); |
| return dc != null && dc.okToDisplay(); |
| } |
| |
| boolean okToAnimate() { |
| return okToAnimate(false /* ignoreFrozen */); |
| } |
| |
| boolean okToAnimate(boolean ignoreFrozen) { |
| final DisplayContent dc = getDisplayContent(); |
| return dc != null && dc.okToAnimate(ignoreFrozen); |
| } |
| |
| @Override |
| public void commitPendingTransaction() { |
| scheduleAnimation(); |
| } |
| |
| void reassignLayer(Transaction t) { |
| final WindowContainer parent = getParent(); |
| if (parent != null) { |
| parent.assignChildLayers(t); |
| } |
| } |
| |
| @Override |
| public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { |
| mLastLayer = -1; |
| reassignLayer(t); |
| } |
| |
| @Override |
| public void onAnimationLeashLost(Transaction t) { |
| mLastLayer = -1; |
| reassignLayer(t); |
| } |
| |
| /** |
| * Called when an animation has finished running. |
| */ |
| protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) { |
| mWmService.onAnimationFinished(); |
| } |
| |
| /** |
| * @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. |
| * |
| * It traverses from the current container to its parents recursively. If nothing is animating, |
| * it will return {@code null}. |
| */ |
| @Nullable |
| WindowContainer getAnimatingContainer() { |
| if (isAnimating()) { |
| return this; |
| } |
| final WindowContainer parent = getParent(); |
| return (parent != null) ? parent.getAnimatingContainer() : null; |
| } |
| |
| /** |
| * @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 + " "); |
| } |
| } |
| |
| void updateSurfacePosition() { |
| if (mSurfaceControl == null) { |
| return; |
| } |
| |
| getRelativeDisplayedPosition(mTmpPos); |
| if (mTmpPos.equals(mLastSurfacePosition)) { |
| return; |
| } |
| |
| getPendingTransaction().setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); |
| mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y); |
| } |
| |
| @VisibleForTesting |
| Point getLastSurfacePosition() { |
| return mLastSurfacePosition; |
| } |
| |
| /** |
| * Displayed bounds specify where to display this container at. It differs from bounds during |
| * certain operations (like animation or interactive dragging). |
| * |
| * @return the bounds to display this container at. |
| */ |
| Rect getDisplayedBounds() { |
| return getBounds(); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| void getRelativeDisplayedPosition(Point outPos) { |
| final Rect dispBounds = getDisplayedBounds(); |
| outPos.set(dispBounds.left, dispBounds.top); |
| final WindowContainer parent = getParent(); |
| if (parent != null) { |
| final Rect parentBounds = parent.getDisplayedBounds(); |
| outPos.offset(-parentBounds.left, -parentBounds.top); |
| } |
| } |
| |
| 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; |
| } |
| |
| /** Cheap way of doing cast and instanceof. */ |
| Task asTask() { |
| return null; |
| } |
| |
| /** Cheap way of doing cast and instanceof. */ |
| ActivityRecord asActivityRecord() { |
| return null; |
| } |
| |
| RemoteToken getRemoteToken() { |
| return mRemoteToken; |
| } |
| |
| static class RemoteToken extends IWindowContainer.Stub { |
| final WeakReference<WindowContainer> mWeakRef; |
| |
| RemoteToken(WindowContainer container) { |
| mWeakRef = new WeakReference<>(container); |
| } |
| |
| WindowContainer getContainer() { |
| return mWeakRef.get(); |
| } |
| |
| static RemoteToken fromBinder(IBinder binder) { |
| return (RemoteToken) binder; |
| } |
| |
| @Override |
| public SurfaceControl getLeash() { |
| throw new RuntimeException("Not implemented"); |
| } |
| |
| @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(); |
| } |
| } |
| } |