| /* |
| * 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); |
| } |
| |
| } |