/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.wm; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.EMPTY; import android.annotation.CallSuper; import android.content.res.Configuration; import android.util.Pools; import com.android.internal.util.ToBooleanFunction; import java.util.Comparator; import java.util.LinkedList; import java.util.function.Consumer; 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 implements Comparable { 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 mParent = null; // 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 mChildren = new WindowList(); /** Contains override configuration settings applied to this window container. */ private Configuration mOverrideConfiguration = new Configuration(); /** * Contains full configuration applied to this window container. Corresponds to full parent's * config with applied {@link #mOverrideConfiguration}. */ private Configuration mFullConfiguration = new Configuration(); /** * Contains merged override configuration settings from the top of the hierarchy down to this * particular instance. It is different from {@link #mFullConfiguration} because it starts from * topmost container's override config instead of global config. */ private Configuration mMergedOverrideConfiguration = new Configuration(); // The specified orientation for this window container. protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED; private final Pools.SynchronizedPool mConsumerWrapperPool = new Pools.SynchronizedPool<>(3); // The owner/creator for this container. No controller if null. private WindowContainerController mController; final protected WindowContainer getParent() { return mParent; } final protected void setParent(WindowContainer parent) { mParent = parent; // Removing parent usually means that we've detached this entity to destroy it or to attach // to another parent. In both cases we don't need to update the configuration now. if (mParent != null) { // Update full configuration of this container and all its children. onConfigurationChanged(mParent.mFullConfiguration); // Update merged override configuration of this container and all its children. onMergedOverrideConfigurationChanged(); } onParentSet(); } /** * 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. */ void onParentSet() { // Do nothing by default. } // Temp. holders for a chain of containers we are currently processing. private final LinkedList mTmpChain1 = new LinkedList(); private final LinkedList 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 comparator) { if (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.getParent() != null) { throw new IllegalArgumentException("addChild: container=" + child.getName() + " is already a child of container=" + child.getParent().getName() + " can't add to container=" + getName()); } mChildren.add(index, child); // Set the parent after we've actually added a child in case a subclass depends on this. child.setParent(this); } /** * 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)) { child.setParent(null); } else { throw new IllegalArgumentException("removeChild: container=" + child.getName() + " is not a child of container=" + getName()); } } /** * Removes this window container and its children with no regard for what else might be going on * in the system. For example, the container will be removed during animation if this method is * called which isn't desirable. For most cases you want to call {@link #removeIfPossible()} * which allows the system to defer removal until a suitable time. */ @CallSuper void removeImmediately() { while (!mChildren.isEmpty()) { final WindowContainer 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. mChildren.remove(child); } if (mParent != null) { mParent.removeChild(this); } if (mController != null) { setController(null); } } /** * 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(WindowContainer child) { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer current = mChildren.get(i); if (current == child || current.hasChild(child)) { return true; } } return false; } /** * Move a child from it's current place in siblings list to the specified position, * with an option to move all its parents to top. * @param position Target position to move the child to. * @param child Child to move to selected position. * @param includingParents Flag indicating whether we need to move the entire branch of the * hierarchy when we're moving a child to {@link #POSITION_TOP} or * {@link #POSITION_BOTTOM}. When moving to other intermediate positions * this flag will do nothing. */ @CallSuper void positionChildAt(int position, E child, boolean includingParents) { if (child.getParent() != this) { throw new IllegalArgumentException("removeChild: container=" + child.getName() + " is not a child of container=" + getName() + " current parent=" + child.getParent()); } if ((position < 0 && position != POSITION_BOTTOM) || (position > mChildren.size() && position != POSITION_TOP)) { throw new IllegalArgumentException("positionAt: invalid position=" + position + ", children number=" + mChildren.size()); } 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); } 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); } if (includingParents && getParent() != null) { getParent().positionChildAt(POSITION_BOTTOM, this /* child */, true /* includingParents */); } break; default: mChildren.remove(child); mChildren.add(position, child); } } /** * Returns full configuration applied to this window container. * This method should be used for getting settings applied in each particular level of the * hierarchy. */ Configuration getConfiguration() { return mFullConfiguration; } /** * Notify that parent config changed and we need to update full configuration. * @see #mFullConfiguration */ void onConfigurationChanged(Configuration newParentConfig) { mFullConfiguration.setTo(newParentConfig); mFullConfiguration.updateFrom(mOverrideConfiguration); for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer child = mChildren.get(i); child.onConfigurationChanged(mFullConfiguration); } } /** Returns override configuration applied to this window container. */ Configuration getOverrideConfiguration() { return mOverrideConfiguration; } /** * Update override configuration and recalculate full config. * @see #mOverrideConfiguration * @see #mFullConfiguration */ void onOverrideConfigurationChanged(Configuration overrideConfiguration) { mOverrideConfiguration.setTo(overrideConfiguration); // Update full configuration of this container and all its children. onConfigurationChanged(mParent != null ? mParent.getConfiguration() : EMPTY); // Update merged override config of this container and all its children. onMergedOverrideConfigurationChanged(); if (mParent != null) { mParent.onDescendantOverrideConfigurationChanged(); } } /** * Notify that a descendant's overrideConfiguration has changed. */ void onDescendantOverrideConfigurationChanged() { if (mParent != null) { mParent.onDescendantOverrideConfigurationChanged(); } } /** * Get merged override configuration from the top of the hierarchy down to this * particular instance. This should be reported to client as override config. */ Configuration getMergedOverrideConfiguration() { return mMergedOverrideConfiguration; } /** * Update merged override configuration based on corresponding parent's config and notify all * its children. If there is no parent, merged override configuration will set equal to current * override config. * @see #mMergedOverrideConfiguration */ private void onMergedOverrideConfigurationChanged() { if (mParent != null) { mMergedOverrideConfiguration.setTo(mParent.getMergedOverrideConfiguration()); mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration); } else { mMergedOverrideConfiguration.setTo(mOverrideConfiguration); } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer child = mChildren.get(i); child.onMergedOverrideConfigurationChanged(); } } /** * Notify that the display this container is on has changed. * @param dc The new display this container is on. */ void onDisplayChanged(DisplayContent dc) { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer child = mChildren.get(i); child.onDisplayChanged(dc); } } void setWaitingForDrawnIfResizingChanged() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.setWaitingForDrawnIfResizingChanged(); } } void onResize() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.onResize(); } } void onMovedByResize() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.onMovedByResize(); } } void resetDragResizingChangeReported() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.resetDragResizingChangeReported(); } } void forceWindowsScaleableInTransaction(boolean force) { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.forceWindowsScaleableInTransaction(force); } } boolean isAnimating() { for (int j = mChildren.size() - 1; j >= 0; j--) { final WindowContainer wc = mChildren.get(j); if (wc.isAnimating()) { return true; } } return false; } void sendAppVisibilityToClients() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.sendAppVisibilityToClients(); } } void setVisibleBeforeClientHidden() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.setVisibleBeforeClientHidden(); } } /** * Returns true if the container or one of its children as some content it can display or wants * to display (e.g. app views or saved surface). * * NOTE: While this method will return true if the there is some content to display, it doesn't * mean the container is visible. Use {@link #isVisible()} to determine if the container is * visible. */ boolean hasContentToDisplay() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); if (wc.hasContentToDisplay()) { return true; } } return false; } /** * Returns true if the container or one of its children is considered visible from the * WindowManager perspective which usually means valid surface and some other internal state * are true. * * NOTE: While this method will return true if the surface is visible, it doesn't mean the * client has actually displayed any content. Use {@link #hasContentToDisplay()} to determine if * the container has any content to display. */ boolean isVisible() { // TODO: Will this be more correct if it checks the visibility of its parents? // It depends...For example, Tasks and Stacks are only visible if there children are visible // but, WindowState are not visible if there parent are not visible. Maybe have the // container specify which direction to traverse for visibility? for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); if (wc.isVisible()) { return true; } } return false; } /** Returns the top child container. */ E getTopChild() { return mChildren.peekLast(); } /** Returns true if there is still a removal being deferred */ boolean checkCompleteDeferredRemoval() { boolean stillDeferringRemoval = false; for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); stillDeferringRemoval |= wc.checkCompleteDeferredRemoval(); } return stillDeferringRemoval; } /** Checks if all windows in an app are all drawn and shows them if needed. */ void checkAppWindowsReadyToShow() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.checkAppWindowsReadyToShow(); } } /** Step currently ongoing animation for App window containers. */ void stepAppWindowsAnimation(long currentTime) { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.stepAppWindowsAnimation(currentTime); } } void onAppTransitionDone() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.onAppTransitionDone(); } } void setOrientation(int orientation) { mOrientation = orientation; } int getOrientation() { return getOrientation(mOrientation); } /** * Returns the specified orientation for this window container or one of its children is there * is one set, or {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSET} if no * specification is set. * NOTE: {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} is a * specification... * * @param candidate The current orientation candidate that will be returned if we don't find a * better match. * @return The orientation as specified by this branch or the window hierarchy. */ int getOrientation(int candidate) { if (!fillsParent()) { // Ignore containers that don't completely fill their parents. return SCREEN_ORIENTATION_UNSET; } // The container fills its parent so we can use it orientation if it has one // specified; otherwise we prefer to use the orientation of its topmost child that has one // specified and fall back on this container's unset or unspecified value as a candidate // if none of the children have a better candidate for the orientation. if (mOrientation != SCREEN_ORIENTATION_UNSET && mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { return mOrientation; } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); // TODO: Maybe mOrientation should default to SCREEN_ORIENTATION_UNSET vs. // SCREEN_ORIENTATION_UNSPECIFIED? final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND ? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET); if (orientation == SCREEN_ORIENTATION_BEHIND) { // container wants us to use the orientation of the container behind it. See if we // can find one. Else return SCREEN_ORIENTATION_BEHIND so the caller can choose to // look behind this container. candidate = orientation; continue; } if (orientation == SCREEN_ORIENTATION_UNSET) { continue; } if (wc.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) { // Use the orientation if the container fills its parent or requested an explicit // orientation that isn't SCREEN_ORIENTATION_UNSPECIFIED. return orientation; } } return candidate; } /** * Returns true if this container is opaque and fills all the space made available by its parent * container. * * NOTE: It is possible for this container to occupy more space than the parent has (or less), * this is just a signal from the client to window manager stating its intent, but not what it * actually does. */ boolean fillsParent() { return false; } // TODO: Users would have their own window containers under the display container? void switchUser() { for (int i = mChildren.size() - 1; i >= 0; --i) { mChildren.get(i).switchUser(); } } /** * 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 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 callback, boolean traverseTopToBottom) { ForAllWindowsConsumerWrapper wrapper = obtainConsumerWrapper(callback); forAllWindows(wrapper, traverseTopToBottom); wrapper.release(); } /** * For all tasks at or below this container call the callback. * * @param callback Callback to be called for every task. */ void forAllTasks(Consumer callback) { for (int i = mChildren.size() - 1; i >= 0; --i) { mChildren.get(i).forAllTasks(callback); } } WindowState getWindow(Predicate callback) { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowState w = mChildren.get(i).getWindow(callback); if (w != null) { return w; } } return null; } /** * Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than * the input container in terms of z-order. */ @Override public int compareTo(WindowContainer other) { if (this == other) { return 0; } if (mParent != null && mParent == other.mParent) { final WindowList list = mParent.mChildren; return list.indexOf(this) > list.indexOf(other) ? 1 : -1; } final LinkedList thisParentChain = mTmpChain1; final LinkedList 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 list = commonAncestor.mChildren; return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast()) ? 1 : -1; } finally { mTmpChain1.clear(); mTmpChain2.clear(); } } private void getParents(LinkedList parents) { parents.clear(); WindowContainer current = this; do { parents.addLast(current); current = current.mParent; } while (current != null); } WindowContainerController getController() { return mController; } void setController(WindowContainerController controller) { if (mController != null && controller != null) { throw new IllegalArgumentException("Can't set controller=" + mController + " for container=" + this + " Already set to=" + mController); } if (controller != null) { controller.setContainer(this); } else if (mController != null) { mController.setContainer(null); } mController = controller; } /** * Dumps the names of this container children in the input print writer indenting each * level with the input prefix. */ void dumpChildrenNames(StringBuilder out, String prefix) { final String childPrefix = prefix + " "; out.append(getName() + "\n"); for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); out.append(childPrefix + "#" + i + " "); wc.dumpChildrenNames(out, childPrefix); } } String getName() { return toString(); } private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer consumer) { ForAllWindowsConsumerWrapper wrapper = mConsumerWrapperPool.acquire(); if (wrapper == null) { wrapper = new ForAllWindowsConsumerWrapper(); } wrapper.setConsumer(consumer); return wrapper; } private final class ForAllWindowsConsumerWrapper implements ToBooleanFunction { private Consumer mConsumer; void setConsumer(Consumer consumer) { mConsumer = consumer; } @Override public boolean apply(WindowState w) { mConsumer.accept(w); return false; } void release() { mConsumer = null; mConsumerWrapperPool.release(this); } } }