blob: be7c18c493736183ff082d4de32fbf755fe65ed8 [file] [log] [blame]
/*
* Copyright (C) 2017 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.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION;
import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.LocaleList;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
* Contains common logic for classes that have override configurations and are organized in a
* hierarchy.
*/
public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
/**
* {@link #Rect} returned from {@link #getRequestedOverrideBounds()} to prevent original value
* from being set directly.
*/
private Rect mReturnBounds = new Rect();
/**
* Contains requested override configuration settings applied to this configuration container.
*/
private Configuration mRequestedOverrideConfiguration = new Configuration();
/**
* Contains the requested override configuration with parent and policy constraints applied.
* This is the set of overrides that gets applied to the full and merged configurations.
*/
private Configuration mResolvedOverrideConfiguration = new Configuration();
/** True if mRequestedOverrideConfiguration is not empty */
private boolean mHasOverrideConfiguration;
/**
* Contains full configuration applied to this configuration container. Corresponds to full
* parent's config with applied {@link #mResolvedOverrideConfiguration}.
*/
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();
private ArrayList<ConfigurationContainerListener> mChangeListeners = new ArrayList<>();
// TODO: Can't have ag/2592611 soon enough!
private final Configuration mRequestsTmpConfig = new Configuration();
private final Configuration mResolvedTmpConfig = new Configuration();
// Used for setting bounds
private final Rect mTmpRect = new Rect();
static final int BOUNDS_CHANGE_NONE = 0;
/**
* Return value from {@link #setBounds(Rect)} indicating the position of the override bounds
* changed.
*/
static final int BOUNDS_CHANGE_POSITION = 1;
/**
* Return value from {@link #setBounds(Rect)} indicating the size of the override bounds
* changed.
*/
static final int BOUNDS_CHANGE_SIZE = 1 << 1;
/**
* Returns full configuration applied to this configuration container.
* This method should be used for getting settings applied in each particular level of the
* hierarchy.
*/
@NonNull
public Configuration getConfiguration() {
return mFullConfiguration;
}
/**
* Notify that parent config changed and we need to update full configuration.
* @see #mFullConfiguration
*/
public void onConfigurationChanged(Configuration newParentConfig) {
mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
resolveOverrideConfiguration(newParentConfig);
mFullConfiguration.setTo(newParentConfig);
// Do not inherit always-on-top property from parent, otherwise the always-on-top
// property is propagated to all children. In that case, newly added child is
// always being positioned at bottom (behind the always-on-top siblings).
mFullConfiguration.windowConfiguration.unsetAlwaysOnTop();
mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
onMergedOverrideConfigurationChanged();
if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
// This depends on the assumption that change-listeners don't do
// their own override resolution. This way, dependent hierarchies
// can stay properly synced-up with a primary hierarchy's constraints.
// Since the hierarchies will be merged, this whole thing will go away
// before the assumption will be broken.
// Inform listeners of the change.
for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
mChangeListeners.get(i).onRequestedOverrideConfigurationChanged(
mResolvedOverrideConfiguration);
}
}
for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
mMergedOverrideConfiguration);
}
for (int i = getChildCount() - 1; i >= 0; --i) {
dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
}
}
/**
* Dispatches the configuration to child when {@link #onConfigurationChanged(Configuration)} is
* called. This allows the derived classes to override how to dispatch the configuration.
*/
void dispatchConfigurationToChild(E child, Configuration config) {
child.onConfigurationChanged(config);
}
/**
* Resolves the current requested override configuration into
* {@link #mResolvedOverrideConfiguration}
*
* @param newParentConfig The new parent configuration to resolve overrides against.
*/
void resolveOverrideConfiguration(Configuration newParentConfig) {
mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration);
}
/** Returns {@code true} if requested override override configuration is not empty. */
boolean hasRequestedOverrideConfiguration() {
return mHasOverrideConfiguration;
}
/** Returns requested override configuration applied to this configuration container. */
@NonNull
public Configuration getRequestedOverrideConfiguration() {
return mRequestedOverrideConfiguration;
}
/** Returns the resolved override configuration. */
@NonNull
Configuration getResolvedOverrideConfiguration() {
return mResolvedOverrideConfiguration;
}
/**
* Update override configuration and recalculate full config.
* @see #mRequestedOverrideConfiguration
* @see #mFullConfiguration
*/
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
updateRequestedOverrideConfiguration(overrideConfiguration);
// Update full configuration of this container and all its children.
final ConfigurationContainer parent = getParent();
onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
}
/** Updates override configuration without recalculate full config. */
void updateRequestedOverrideConfiguration(Configuration overrideConfiguration) {
// Pre-compute this here, so we don't need to go through the entire Configuration when
// writing to proto (which has significant cost if we write a lot of empty configurations).
mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
mRequestedOverrideConfiguration.setTo(overrideConfiguration);
final Rect newBounds = mRequestedOverrideConfiguration.windowConfiguration.getBounds();
if (mHasOverrideConfiguration && providesMaxBounds()
&& diffRequestedOverrideMaxBounds(newBounds) != BOUNDS_CHANGE_NONE) {
mRequestedOverrideConfiguration.windowConfiguration.setMaxBounds(newBounds);
}
}
/**
* Get merged override configuration from the top of the hierarchy down to this particular
* instance. This should be reported to client as override config.
*/
@NonNull
public Configuration getMergedOverrideConfiguration() {
return mMergedOverrideConfiguration;
}
/**
* Update merged override configuration based on corresponding parent's config. If there is no
* parent, merged override configuration will set equal to current override config. This
* doesn't cascade on its own since it's called by {@link #onConfigurationChanged}.
* @see #mMergedOverrideConfiguration
*/
void onMergedOverrideConfigurationChanged() {
final ConfigurationContainer parent = getParent();
if (parent != null) {
mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration());
// Do not inherit always-on-top property from parent, otherwise the always-on-top
// property is propagated to all children. In that case, newly added child is
// always being positioned at bottom (behind the always-on-top siblings).
mMergedOverrideConfiguration.windowConfiguration.unsetAlwaysOnTop();
mMergedOverrideConfiguration.updateFrom(mResolvedOverrideConfiguration);
} else {
mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration);
}
}
/**
* Indicates whether this container chooses not to override any bounds from its parent, either
* because it doesn't request to override them or the request is dropped during configuration
* resolution. In this case, it will inherit the bounds of the first ancestor which specifies a
* bounds subject to policy constraints.
*
* @return {@code true} if this container level uses bounds from parent level. {@code false}
* otherwise.
*/
public boolean matchParentBounds() {
return getResolvedOverrideBounds().isEmpty();
}
/**
* Returns whether the bounds specified are considered the same as the existing requested
* override bounds. This is either when the two bounds are equal or the requested override
* bounds are empty and the specified bounds is null.
*
* @return {@code true} if the bounds are equivalent, {@code false} otherwise
*/
public boolean equivalentRequestedOverrideBounds(Rect bounds) {
return equivalentBounds(getRequestedOverrideBounds(), bounds);
}
/** Similar to {@link #equivalentRequestedOverrideBounds(Rect)}, but compares max bounds. */
public boolean equivalentRequestedOverrideMaxBounds(Rect bounds) {
return equivalentBounds(getRequestedOverrideMaxBounds(), bounds);
}
/**
* Returns whether the two bounds are equal to each other or are a combination of null or empty.
*/
public static boolean equivalentBounds(Rect bounds, Rect other) {
return bounds == other
|| (bounds != null && (bounds.equals(other) || (bounds.isEmpty() && other == null)))
|| (other != null && other.isEmpty() && bounds == null);
}
/**
* Returns the effective bounds of this container, inheriting the first non-empty bounds set in
* its ancestral hierarchy, including itself.
*/
public Rect getBounds() {
mReturnBounds.set(getConfiguration().windowConfiguration.getBounds());
return mReturnBounds;
}
public void getBounds(Rect outBounds) {
outBounds.set(getBounds());
}
/** Similar to {@link #getBounds()}, but reports the max bounds. */
public Rect getMaxBounds() {
mReturnBounds.set(getConfiguration().windowConfiguration.getMaxBounds());
return mReturnBounds;
}
/**
* Sets {@code out} to the top-left corner of the bounds as returned by {@link #getBounds()}.
*/
public void getPosition(Point out) {
Rect bounds = getBounds();
out.set(bounds.left, bounds.top);
}
Rect getResolvedOverrideBounds() {
mReturnBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
return mReturnBounds;
}
/**
* Returns the bounds requested on this container. These may not be the actual bounds the
* container ends up with due to policy constraints. The {@link Rect} handed back is
* shared for all calls to this method and should not be modified.
*/
public Rect getRequestedOverrideBounds() {
mReturnBounds.set(getRequestedOverrideConfiguration().windowConfiguration.getBounds());
return mReturnBounds;
}
/** Similar to {@link #getRequestedOverrideBounds()}, but returns the max bounds. */
public Rect getRequestedOverrideMaxBounds() {
mReturnBounds.set(getRequestedOverrideConfiguration().windowConfiguration.getMaxBounds());
return mReturnBounds;
}
/**
* Returns {@code true} if the {@link WindowConfiguration} in the requested override
* {@link Configuration} specifies bounds.
*/
public boolean hasOverrideBounds() {
return !getRequestedOverrideBounds().isEmpty();
}
/**
* Sets the passed in {@link Rect} to the current bounds.
* @see #getRequestedOverrideBounds()
*/
public void getRequestedOverrideBounds(Rect outBounds) {
outBounds.set(getRequestedOverrideBounds());
}
/**
* Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor.
* This value will be reported when {@link #getBounds()} and
* {@link #getRequestedOverrideBounds()}. If
* an empty {@link Rect} or null is specified, this container will be considered to match its
* parent bounds {@see #matchParentBounds} and will inherit bounds from its parent.
*
* @param bounds The bounds defining the container size.
*
* @return a bitmask representing the types of changes made to the bounds.
*/
public int setBounds(Rect bounds) {
int boundsChange = diffRequestedOverrideBounds(bounds);
final boolean overrideMaxBounds = providesMaxBounds()
&& diffRequestedOverrideMaxBounds(bounds) != BOUNDS_CHANGE_NONE;
if (boundsChange == BOUNDS_CHANGE_NONE && !overrideMaxBounds) {
return boundsChange;
}
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
mRequestsTmpConfig.windowConfiguration.setBounds(bounds);
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
return boundsChange;
}
public int setBounds(int left, int top, int right, int bottom) {
mTmpRect.set(left, top, right, bottom);
return setBounds(mTmpRect);
}
/**
* Returns {@code true} if this {@link ConfigurationContainer} provides the maximum bounds to
* its child {@link ConfigurationContainer}s. Returns {@code false}, otherwise.
* <p>
* The maximum bounds is how large a window can be expanded.
* </p>
*/
protected boolean providesMaxBounds() {
return false;
}
int diffRequestedOverrideMaxBounds(Rect bounds) {
if (equivalentRequestedOverrideMaxBounds(bounds)) {
return BOUNDS_CHANGE_NONE;
}
int boundsChange = BOUNDS_CHANGE_NONE;
final Rect existingBounds = getRequestedOverrideMaxBounds();
if (bounds == null || existingBounds.left != bounds.left
|| existingBounds.top != bounds.top) {
boundsChange |= BOUNDS_CHANGE_POSITION;
}
if (bounds == null || existingBounds.width() != bounds.width()
|| existingBounds.height() != bounds.height()) {
boundsChange |= BOUNDS_CHANGE_SIZE;
}
return boundsChange;
}
int diffRequestedOverrideBounds(Rect bounds) {
if (equivalentRequestedOverrideBounds(bounds)) {
return BOUNDS_CHANGE_NONE;
}
int boundsChange = BOUNDS_CHANGE_NONE;
final Rect existingBounds = getRequestedOverrideBounds();
if (bounds == null || existingBounds.left != bounds.left
|| existingBounds.top != bounds.top) {
boundsChange |= BOUNDS_CHANGE_POSITION;
}
if (bounds == null || existingBounds.width() != bounds.width()
|| existingBounds.height() != bounds.height()) {
boundsChange |= BOUNDS_CHANGE_SIZE;
}
return boundsChange;
}
public WindowConfiguration getWindowConfiguration() {
return mFullConfiguration.windowConfiguration;
}
/** Returns the windowing mode the configuration container is currently in. */
public int getWindowingMode() {
return mFullConfiguration.windowConfiguration.getWindowingMode();
}
/** Returns the windowing mode override that is requested by this container. */
public int getRequestedOverrideWindowingMode() {
return mRequestedOverrideConfiguration.windowConfiguration.getWindowingMode();
}
/** Sets the requested windowing mode override for the configuration container. */
public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) {
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
mRequestsTmpConfig.windowConfiguration.setWindowingMode(windowingMode);
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
}
/** Sets the always on top flag for this configuration container.
* When you call this function, make sure that the following functions are called as well to
* keep proper z-order.
* - {@link TaskDisplayArea#positionChildAt(int POSITION_TOP, Task, boolean)};
* */
public void setAlwaysOnTop(boolean alwaysOnTop) {
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
mRequestsTmpConfig.windowConfiguration.setAlwaysOnTop(alwaysOnTop);
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
}
/** Sets the windowing mode for the configuration container. */
void setDisplayWindowingMode(int windowingMode) {
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
mRequestsTmpConfig.windowConfiguration.setDisplayWindowingMode(windowingMode);
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
}
/**
* Returns true if this container is currently in multi-window mode. I.e. sharing the screen
* with another activity.
*/
public boolean inMultiWindowMode() {
/*@WindowConfiguration.WindowingMode*/ int windowingMode =
mFullConfiguration.windowConfiguration.getWindowingMode();
return WindowConfiguration.inMultiWindowMode(windowingMode);
}
public boolean inPinnedWindowingMode() {
return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
}
public boolean inFreeformWindowingMode() {
return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
}
/** Returns the activity type associated with the configuration container. */
/*@WindowConfiguration.ActivityType*/
public int getActivityType() {
return mFullConfiguration.windowConfiguration.getActivityType();
}
/** Sets the activity type to associate with the configuration container. */
public void setActivityType(/*@WindowConfiguration.ActivityType*/ int activityType) {
int currentActivityType = getActivityType();
if (currentActivityType == activityType) {
return;
}
if (currentActivityType != ACTIVITY_TYPE_UNDEFINED) {
throw new IllegalStateException("Can't change activity type once set: " + this
+ " activityType=" + activityTypeToString(activityType));
}
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
mRequestsTmpConfig.windowConfiguration.setActivityType(activityType);
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
}
public boolean isActivityTypeHome() {
return getActivityType() == ACTIVITY_TYPE_HOME;
}
public boolean isActivityTypeRecents() {
return getActivityType() == ACTIVITY_TYPE_RECENTS;
}
final boolean isActivityTypeHomeOrRecents() {
final int activityType = getActivityType();
return activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
}
public boolean isActivityTypeAssistant() {
return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
}
/**
* Applies app-specific nightMode and {@link LocaleList} on requested configuration.
* @return true if any of the requested configuration has been updated.
*/
public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales,
@Configuration.GrammaticalGender Integer gender) {
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
nightMode);
boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
locales);
boolean newGenderSet = (gender != null) && setOverrideGender(mRequestsTmpConfig,
gender);
if (newNightModeSet || newLocalesSet || newGenderSet) {
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
}
return newNightModeSet || newLocalesSet || newGenderSet;
}
/**
* Overrides the night mode applied to this ConfigurationContainer.
* @return true if the nightMode has been changed.
*/
private boolean setOverrideNightMode(Configuration requestsTmpConfig, int nightMode) {
final int currentUiMode = mRequestedOverrideConfiguration.uiMode;
final int currentNightMode = currentUiMode & Configuration.UI_MODE_NIGHT_MASK;
final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentNightMode == validNightMode) {
return false;
}
requestsTmpConfig.uiMode = validNightMode
| (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK);
return true;
}
/**
* Overrides the locales applied to this ConfigurationContainer.
* @return true if the LocaleList has been changed.
*/
private boolean setOverrideLocales(Configuration requestsTmpConfig,
@NonNull LocaleList overrideLocales) {
if (mRequestedOverrideConfiguration.getLocales().equals(overrideLocales)) {
return false;
}
requestsTmpConfig.setLocales(overrideLocales);
requestsTmpConfig.userSetLocale = true;
return true;
}
/**
* Overrides the gender to this ConfigurationContainer.
*
* @return true if the grammatical gender has been changed.
*/
private boolean setOverrideGender(Configuration requestsTmpConfig,
@Configuration.GrammaticalGender int gender) {
if (mRequestedOverrideConfiguration.getGrammaticalGender() == gender) {
return false;
} else {
requestsTmpConfig.setGrammaticalGender(gender);
return true;
}
}
public boolean isActivityTypeDream() {
return getActivityType() == ACTIVITY_TYPE_DREAM;
}
public boolean isActivityTypeStandard() {
return getActivityType() == ACTIVITY_TYPE_STANDARD;
}
public boolean isActivityTypeStandardOrUndefined() {
/*@WindowConfiguration.ActivityType*/ final int activityType = getActivityType();
return activityType == ACTIVITY_TYPE_STANDARD || activityType == ACTIVITY_TYPE_UNDEFINED;
}
public static boolean isCompatibleActivityType(int currentType, int otherType) {
if (currentType == otherType) {
return true;
}
if (currentType == ACTIVITY_TYPE_ASSISTANT) {
// Assistant activities are only compatible with themselves...
return false;
}
// Otherwise we are compatible if us or other is not currently defined.
return currentType == ACTIVITY_TYPE_UNDEFINED || otherType == ACTIVITY_TYPE_UNDEFINED;
}
/**
* Returns true if this container is compatible with the input windowing mode and activity type.
* The container is compatible:
* - If {@param activityType} and {@param windowingMode} match this container activity type and
* windowing mode.
* - If {@param activityType} is {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
* {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} and this containers activity type is also
* standard or undefined and its windowing mode matches {@param windowingMode}.
* - If {@param activityType} isn't {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
* {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} or this containers activity type isn't
* also standard or undefined and its activity type matches {@param activityType} regardless of
* if {@param windowingMode} matches the containers windowing mode.
*/
public boolean isCompatible(int windowingMode, int activityType) {
final int thisActivityType = getActivityType();
final int thisWindowingMode = getWindowingMode();
final boolean sameActivityType = thisActivityType == activityType;
final boolean sameWindowingMode = thisWindowingMode == windowingMode;
if (sameActivityType && sameWindowingMode) {
return true;
}
if ((activityType != ACTIVITY_TYPE_UNDEFINED && activityType != ACTIVITY_TYPE_STANDARD)
|| !isActivityTypeStandardOrUndefined()) {
// Only activity type need to match for non-standard activity types that are defined.
return sameActivityType;
}
// Otherwise we are compatible if the windowing mode is the same.
return sameWindowingMode;
}
void registerConfigurationChangeListener(ConfigurationContainerListener listener) {
registerConfigurationChangeListener(listener, true /* shouldDispatchConfig */);
}
void registerConfigurationChangeListener(ConfigurationContainerListener listener,
boolean shouldDispatchConfig) {
if (mChangeListeners.contains(listener)) {
return;
}
mChangeListeners.add(listener);
if (shouldDispatchConfig) {
listener.onRequestedOverrideConfigurationChanged(mResolvedOverrideConfiguration);
listener.onMergedOverrideConfigurationChanged(mMergedOverrideConfiguration);
}
}
void unregisterConfigurationChangeListener(ConfigurationContainerListener listener) {
mChangeListeners.remove(listener);
}
@VisibleForTesting
boolean containsListener(ConfigurationContainerListener listener) {
return mChangeListeners.contains(listener);
}
/**
* Must be called when new parent for the container was set.
*/
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
// 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 (newParent != null) {
// Update full configuration of this container and all its children.
onConfigurationChanged(newParent.mFullConfiguration);
}
}
/**
* Write to a protocol buffer output stream. Protocol buffer message definition is at
* {@link com.android.server.wm.ConfigurationContainerProto}.
*
* @param proto Stream to write the ConfigurationContainer object to.
* @param fieldId Field Id of the ConfigurationContainer as defined in the parent
* message.
* @param logLevel Determines the amount of data to be written to the Protobuf.
* @hide
*/
@CallSuper
protected void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
final long token = proto.start(fieldId);
if (logLevel == WindowTraceLogLevel.ALL || mHasOverrideConfiguration) {
mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION,
logLevel == WindowTraceLogLevel.CRITICAL);
}
// Unless trace level is set to `WindowTraceLogLevel.ALL` don't dump anything that isn't
// required to mitigate performance overhead
if (logLevel == WindowTraceLogLevel.ALL) {
mFullConfiguration.dumpDebug(proto, FULL_CONFIGURATION, false /* critical */);
mMergedOverrideConfiguration.dumpDebug(proto, MERGED_OVERRIDE_CONFIGURATION,
false /* critical */);
}
if (logLevel == WindowTraceLogLevel.TRIM) {
// Required for Fass to automatically detect pip transitions in Winscope traces
dumpDebugWindowingMode(proto);
}
proto.end(token);
}
private void dumpDebugWindowingMode(ProtoOutputStream proto) {
final long fullConfigToken = proto.start(FULL_CONFIGURATION);
final long windowConfigToken = proto.start(WINDOW_CONFIGURATION);
int windowingMode = mFullConfiguration.windowConfiguration.getWindowingMode();
proto.write(WINDOWING_MODE, windowingMode);
proto.end(windowConfigToken);
proto.end(fullConfigToken);
}
/**
* Dumps the names of this container children in the input print writer indenting each
* level with the input prefix.
*/
public void dumpChildrenNames(PrintWriter pw, String prefix) {
dumpChildrenNames(pw, prefix, true /* isLastChild */);
}
/**
* Dumps the names of this container children in the input print writer indenting each
* level with the input prefix.
*/
public void dumpChildrenNames(PrintWriter pw, String prefix, boolean isLastChild) {
int curWinMode = getWindowingMode();
String winMode = windowingModeToString(curWinMode);
if (curWinMode != WINDOWING_MODE_UNDEFINED &&
curWinMode != WINDOWING_MODE_FULLSCREEN) {
winMode = winMode.toUpperCase();
}
int requestedWinMode = getRequestedOverrideWindowingMode();
String overrideWinMode = windowingModeToString(requestedWinMode);
if (requestedWinMode != WINDOWING_MODE_UNDEFINED &&
requestedWinMode != WINDOWING_MODE_FULLSCREEN) {
overrideWinMode = overrideWinMode.toUpperCase();
}
String actType = activityTypeToString(getActivityType());
if (getActivityType() != ACTIVITY_TYPE_UNDEFINED
&& getActivityType() != ACTIVITY_TYPE_STANDARD) {
actType = actType.toUpperCase();
}
pw.print(prefix + (isLastChild ? "└─ " : "├─ "));
pw.println(getName()
+ " type=" + actType
+ " mode=" + winMode
+ " override-mode=" + overrideWinMode
+ " requested-bounds=" + getRequestedOverrideBounds().toShortString()
+ " bounds=" + getBounds().toShortString());
String childPrefix = prefix + (isLastChild ? " " : "│ ");
for (int i = getChildCount() - 1; i >= 0; --i) {
final E cc = getChildAt(i);
cc.dumpChildrenNames(pw, childPrefix, i == 0 /* isLastChild */);
}
}
String getName() {
return toString();
}
public boolean isAlwaysOnTop() {
return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
}
boolean hasChild() {
return getChildCount() > 0;
}
abstract protected int getChildCount();
abstract protected E getChildAt(int index);
abstract protected ConfigurationContainer getParent();
}