Home | History | Annotate | Download | only in wm
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.server.wm;
     18 
     19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
     20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
     21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
     22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
     23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
     24 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
     25 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
     26 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
     27 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
     28 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
     29 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
     30 import static android.app.WindowConfiguration.activityTypeToString;
     31 import static android.app.WindowConfiguration.windowingModeToString;
     32 import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION;
     33 import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
     34 import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
     35 
     36 import android.annotation.CallSuper;
     37 import android.app.WindowConfiguration;
     38 import android.content.res.Configuration;
     39 import android.graphics.Rect;
     40 import android.util.proto.ProtoOutputStream;
     41 
     42 import java.io.PrintWriter;
     43 import java.util.ArrayList;
     44 
     45 /**
     46  * Contains common logic for classes that have override configurations and are organized in a
     47  * hierarchy.
     48  */
     49 public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
     50     /**
     51      * {@link #Rect} returned from {@link #getOverrideBounds()} to prevent original value from being
     52      * set directly.
     53      */
     54     private Rect mReturnBounds = new Rect();
     55 
     56     /** Contains override configuration settings applied to this configuration container. */
     57     private Configuration mOverrideConfiguration = new Configuration();
     58 
     59     /** True if mOverrideConfiguration is not empty */
     60     private boolean mHasOverrideConfiguration;
     61 
     62     /**
     63      * Contains full configuration applied to this configuration container. Corresponds to full
     64      * parent's config with applied {@link #mOverrideConfiguration}.
     65      */
     66     private Configuration mFullConfiguration = new Configuration();
     67 
     68     /**
     69      * Contains merged override configuration settings from the top of the hierarchy down to this
     70      * particular instance. It is different from {@link #mFullConfiguration} because it starts from
     71      * topmost container's override config instead of global config.
     72      */
     73     private Configuration mMergedOverrideConfiguration = new Configuration();
     74 
     75     private ArrayList<ConfigurationContainerListener> mChangeListeners = new ArrayList<>();
     76 
     77     // TODO: Can't have ag/2592611 soon enough!
     78     private final Configuration mTmpConfig = new Configuration();
     79 
     80     // Used for setting bounds
     81     private final Rect mTmpRect = new Rect();
     82 
     83     static final int BOUNDS_CHANGE_NONE = 0;
     84     // Return value from {@link setBounds} indicating the position of the override bounds changed.
     85     static final int BOUNDS_CHANGE_POSITION = 1;
     86     // Return value from {@link setBounds} indicating the size of the override bounds changed.
     87     static final int BOUNDS_CHANGE_SIZE = 1 << 1;
     88 
     89 
     90     /**
     91      * Returns full configuration applied to this configuration container.
     92      * This method should be used for getting settings applied in each particular level of the
     93      * hierarchy.
     94      */
     95     public Configuration getConfiguration() {
     96         return mFullConfiguration;
     97     }
     98 
     99     /**
    100      * Notify that parent config changed and we need to update full configuration.
    101      * @see #mFullConfiguration
    102      */
    103     public void onConfigurationChanged(Configuration newParentConfig) {
    104         mFullConfiguration.setTo(newParentConfig);
    105         mFullConfiguration.updateFrom(mOverrideConfiguration);
    106         for (int i = getChildCount() - 1; i >= 0; --i) {
    107             final ConfigurationContainer child = getChildAt(i);
    108             child.onConfigurationChanged(mFullConfiguration);
    109         }
    110     }
    111 
    112     /** Returns override configuration applied to this configuration container. */
    113     public Configuration getOverrideConfiguration() {
    114         return mOverrideConfiguration;
    115     }
    116 
    117     /**
    118      * Update override configuration and recalculate full config.
    119      * @see #mOverrideConfiguration
    120      * @see #mFullConfiguration
    121      */
    122     public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
    123         // Pre-compute this here, so we don't need to go through the entire Configuration when
    124         // writing to proto (which has significant cost if we write a lot of empty configurations).
    125         mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
    126         mOverrideConfiguration.setTo(overrideConfiguration);
    127         // Update full configuration of this container and all its children.
    128         final ConfigurationContainer parent = getParent();
    129         onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
    130         // Update merged override config of this container and all its children.
    131         onMergedOverrideConfigurationChanged();
    132 
    133         // Use the updated override configuration to notify listeners.
    134         mTmpConfig.setTo(mOverrideConfiguration);
    135         // Inform listeners of the change.
    136         for (int i = mChangeListeners.size() - 1; i >=0; --i) {
    137             mChangeListeners.get(i).onOverrideConfigurationChanged(mTmpConfig);
    138         }
    139     }
    140 
    141     /**
    142      * Get merged override configuration from the top of the hierarchy down to this particular
    143      * instance. This should be reported to client as override config.
    144      */
    145     public Configuration getMergedOverrideConfiguration() {
    146         return mMergedOverrideConfiguration;
    147     }
    148 
    149     /**
    150      * Update merged override configuration based on corresponding parent's config and notify all
    151      * its children. If there is no parent, merged override configuration will set equal to current
    152      * override config.
    153      * @see #mMergedOverrideConfiguration
    154      */
    155     void onMergedOverrideConfigurationChanged() {
    156         final ConfigurationContainer parent = getParent();
    157         if (parent != null) {
    158             mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration());
    159             mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration);
    160         } else {
    161             mMergedOverrideConfiguration.setTo(mOverrideConfiguration);
    162         }
    163         for (int i = getChildCount() - 1; i >= 0; --i) {
    164             final ConfigurationContainer child = getChildAt(i);
    165             child.onMergedOverrideConfigurationChanged();
    166         }
    167     }
    168 
    169     /**
    170      * Indicates whether this container has not specified any bounds different from its parent. In
    171      * this case, it will inherit the bounds of the first ancestor which specifies a bounds.
    172      * @return {@code true} if no explicit bounds have been set at this container level.
    173      *         {@code false} otherwise.
    174      */
    175     public boolean matchParentBounds() {
    176         return getOverrideBounds().isEmpty();
    177     }
    178 
    179     /**
    180      * Returns whether the bounds specified is considered the same as the existing override bounds.
    181      * This is either when the two bounds are equal or the override bounds is empty and the
    182      * specified bounds is null.
    183      *
    184      * @return {@code true} if the bounds are equivalent, {@code false} otherwise
    185      */
    186     public boolean equivalentOverrideBounds(Rect bounds) {
    187         return equivalentBounds(getOverrideBounds(),  bounds);
    188     }
    189 
    190     /**
    191      * Returns whether the two bounds are equal to each other or are a combination of null or empty.
    192      */
    193     public static boolean equivalentBounds(Rect bounds, Rect other) {
    194         return bounds == other
    195                 || (bounds != null && (bounds.equals(other) || (bounds.isEmpty() && other == null)))
    196                 || (other != null && other.isEmpty() && bounds == null);
    197     }
    198 
    199     /**
    200      * Returns the effective bounds of this container, inheriting the first non-empty bounds set in
    201      * its ancestral hierarchy, including itself.
    202      * @return
    203      */
    204     public Rect getBounds() {
    205         mReturnBounds.set(getConfiguration().windowConfiguration.getBounds());
    206         return mReturnBounds;
    207     }
    208 
    209     public void getBounds(Rect outBounds) {
    210         outBounds.set(getBounds());
    211     }
    212 
    213     /**
    214      * Returns the current bounds explicitly set on this container. The {@link Rect} handed back is
    215      * shared for all calls to this method and should not be modified.
    216      */
    217     public Rect getOverrideBounds() {
    218         mReturnBounds.set(getOverrideConfiguration().windowConfiguration.getBounds());
    219 
    220         return mReturnBounds;
    221     }
    222 
    223     /**
    224      * Returns {@code true} if the {@link WindowConfiguration} in the override
    225      * {@link Configuration} specifies bounds.
    226      */
    227     public boolean hasOverrideBounds() {
    228         return !getOverrideBounds().isEmpty();
    229     }
    230 
    231     /**
    232      * Sets the passed in {@link Rect} to the current bounds.
    233      * @see {@link #getOverrideBounds()}.
    234      */
    235     public void getOverrideBounds(Rect outBounds) {
    236         outBounds.set(getOverrideBounds());
    237     }
    238 
    239     /**
    240      * Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor.
    241      * This value will be reported when {@link #getBounds()} and {@link #getOverrideBounds()}. If
    242      * an empty {@link Rect} or null is specified, this container will be considered to match its
    243      * parent bounds {@see #matchParentBounds} and will inherit bounds from its parent.
    244      * @param bounds The bounds defining the container size.
    245      * @return a bitmask representing the types of changes made to the bounds.
    246      */
    247     public int setBounds(Rect bounds) {
    248         int boundsChange = diffOverrideBounds(bounds);
    249 
    250         if (boundsChange == BOUNDS_CHANGE_NONE) {
    251             return boundsChange;
    252         }
    253 
    254 
    255         mTmpConfig.setTo(getOverrideConfiguration());
    256         mTmpConfig.windowConfiguration.setBounds(bounds);
    257         onOverrideConfigurationChanged(mTmpConfig);
    258 
    259         return boundsChange;
    260     }
    261 
    262     public int setBounds(int left, int top, int right, int bottom) {
    263         mTmpRect.set(left, top, right, bottom);
    264         return setBounds(mTmpRect);
    265     }
    266 
    267     int diffOverrideBounds(Rect bounds) {
    268         if (equivalentOverrideBounds(bounds)) {
    269             return BOUNDS_CHANGE_NONE;
    270         }
    271 
    272         int boundsChange = BOUNDS_CHANGE_NONE;
    273 
    274         final Rect existingBounds = getOverrideBounds();
    275 
    276         if (bounds == null || existingBounds.left != bounds.left
    277                 || existingBounds.top != bounds.top) {
    278             boundsChange |= BOUNDS_CHANGE_POSITION;
    279         }
    280 
    281         if (bounds == null || existingBounds.width() != bounds.width()
    282                 || existingBounds.height() != bounds.height()) {
    283             boundsChange |= BOUNDS_CHANGE_SIZE;
    284         }
    285 
    286         return boundsChange;
    287     }
    288 
    289     public WindowConfiguration getWindowConfiguration() {
    290         return mFullConfiguration.windowConfiguration;
    291     }
    292 
    293     /** Returns the windowing mode the configuration container is currently in. */
    294     public int getWindowingMode() {
    295         return mFullConfiguration.windowConfiguration.getWindowingMode();
    296     }
    297 
    298     /** Sets the windowing mode for the configuration container. */
    299     public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) {
    300         mTmpConfig.setTo(getOverrideConfiguration());
    301         mTmpConfig.windowConfiguration.setWindowingMode(windowingMode);
    302         onOverrideConfigurationChanged(mTmpConfig);
    303     }
    304 
    305     /**
    306      * Returns true if this container is currently in multi-window mode. I.e. sharing the screen
    307      * with another activity.
    308      */
    309     public boolean inMultiWindowMode() {
    310         /*@WindowConfiguration.WindowingMode*/ int windowingMode =
    311                 mFullConfiguration.windowConfiguration.getWindowingMode();
    312         return windowingMode != WINDOWING_MODE_FULLSCREEN
    313                 && windowingMode != WINDOWING_MODE_UNDEFINED;
    314     }
    315 
    316     /** Returns true if this container is currently in split-screen windowing mode. */
    317     public boolean inSplitScreenWindowingMode() {
    318         /*@WindowConfiguration.WindowingMode*/ int windowingMode =
    319                 mFullConfiguration.windowConfiguration.getWindowingMode();
    320 
    321         return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
    322                 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
    323     }
    324 
    325     /** Returns true if this container is currently in split-screen secondary windowing mode. */
    326     public boolean inSplitScreenSecondaryWindowingMode() {
    327         /*@WindowConfiguration.WindowingMode*/ int windowingMode =
    328                 mFullConfiguration.windowConfiguration.getWindowingMode();
    329 
    330         return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
    331     }
    332 
    333     public boolean inSplitScreenPrimaryWindowingMode() {
    334         return mFullConfiguration.windowConfiguration.getWindowingMode()
    335                 == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
    336     }
    337 
    338     /**
    339      * Returns true if this container can be put in either
    340      * {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
    341      * {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on
    342      * its current state.
    343      */
    344     public boolean supportsSplitScreenWindowingMode() {
    345         return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
    346     }
    347 
    348     public boolean inPinnedWindowingMode() {
    349         return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
    350     }
    351 
    352     public boolean inFreeformWindowingMode() {
    353         return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
    354     }
    355 
    356     /** Returns the activity type associated with the the configuration container. */
    357     /*@WindowConfiguration.ActivityType*/
    358     public int getActivityType() {
    359         return mFullConfiguration.windowConfiguration.getActivityType();
    360     }
    361 
    362     /** Sets the activity type to associate with the configuration container. */
    363     public void setActivityType(/*@WindowConfiguration.ActivityType*/ int activityType) {
    364         int currentActivityType = getActivityType();
    365         if (currentActivityType == activityType) {
    366             return;
    367         }
    368         if (currentActivityType != ACTIVITY_TYPE_UNDEFINED) {
    369             throw new IllegalStateException("Can't change activity type once set: " + this
    370                     + " activityType=" + activityTypeToString(activityType));
    371         }
    372         mTmpConfig.setTo(getOverrideConfiguration());
    373         mTmpConfig.windowConfiguration.setActivityType(activityType);
    374         onOverrideConfigurationChanged(mTmpConfig);
    375     }
    376 
    377     public boolean isActivityTypeHome() {
    378         return getActivityType() == ACTIVITY_TYPE_HOME;
    379     }
    380 
    381     public boolean isActivityTypeRecents() {
    382         return getActivityType() == ACTIVITY_TYPE_RECENTS;
    383     }
    384 
    385     public boolean isActivityTypeAssistant() {
    386         return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
    387     }
    388 
    389     public boolean isActivityTypeStandard() {
    390         return getActivityType() == ACTIVITY_TYPE_STANDARD;
    391     }
    392 
    393     public boolean isActivityTypeStandardOrUndefined() {
    394         /*@WindowConfiguration.ActivityType*/ final int activityType = getActivityType();
    395         return activityType == ACTIVITY_TYPE_STANDARD || activityType == ACTIVITY_TYPE_UNDEFINED;
    396     }
    397 
    398     public boolean hasCompatibleActivityType(ConfigurationContainer other) {
    399         /*@WindowConfiguration.ActivityType*/ int thisType = getActivityType();
    400         /*@WindowConfiguration.ActivityType*/ int otherType = other.getActivityType();
    401 
    402         if (thisType == otherType) {
    403             return true;
    404         }
    405         if (thisType == ACTIVITY_TYPE_ASSISTANT) {
    406             // Assistant activities are only compatible with themselves...
    407             return false;
    408         }
    409         // Otherwise we are compatible if us or other is not currently defined.
    410         return thisType == ACTIVITY_TYPE_UNDEFINED || otherType == ACTIVITY_TYPE_UNDEFINED;
    411     }
    412 
    413     /**
    414      * Returns true if this container is compatible with the input windowing mode and activity type.
    415      * The container is compatible:
    416      * - If {@param activityType} and {@param windowingMode} match this container activity type and
    417      * windowing mode.
    418      * - If {@param activityType} is {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
    419      * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} and this containers activity type is also
    420      * standard or undefined and its windowing mode matches {@param windowingMode}.
    421      * - If {@param activityType} isn't {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
    422      * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} or this containers activity type isn't
    423      * also standard or undefined and its activity type matches {@param activityType} regardless of
    424      * if {@param windowingMode} matches the containers windowing mode.
    425      */
    426     public boolean isCompatible(int windowingMode, int activityType) {
    427         final int thisActivityType = getActivityType();
    428         final int thisWindowingMode = getWindowingMode();
    429         final boolean sameActivityType = thisActivityType == activityType;
    430         final boolean sameWindowingMode = thisWindowingMode == windowingMode;
    431 
    432         if (sameActivityType && sameWindowingMode) {
    433             return true;
    434         }
    435 
    436         if ((activityType != ACTIVITY_TYPE_UNDEFINED && activityType != ACTIVITY_TYPE_STANDARD)
    437                 || !isActivityTypeStandardOrUndefined()) {
    438             // Only activity type need to match for non-standard activity types that are defined.
    439             return sameActivityType;
    440         }
    441 
    442         // Otherwise we are compatible if the windowing mode is the same.
    443         return sameWindowingMode;
    444     }
    445 
    446     public void registerConfigurationChangeListener(ConfigurationContainerListener listener) {
    447         if (mChangeListeners.contains(listener)) {
    448             return;
    449         }
    450         mChangeListeners.add(listener);
    451         listener.onOverrideConfigurationChanged(mOverrideConfiguration);
    452     }
    453 
    454     public void unregisterConfigurationChangeListener(ConfigurationContainerListener listener) {
    455         mChangeListeners.remove(listener);
    456     }
    457 
    458     /**
    459      * Must be called when new parent for the container was set.
    460      */
    461     protected void onParentChanged() {
    462         final ConfigurationContainer parent = getParent();
    463         // Removing parent usually means that we've detached this entity to destroy it or to attach
    464         // to another parent. In both cases we don't need to update the configuration now.
    465         if (parent != null) {
    466             // Update full configuration of this container and all its children.
    467             onConfigurationChanged(parent.mFullConfiguration);
    468             // Update merged override configuration of this container and all its children.
    469             onMergedOverrideConfigurationChanged();
    470         }
    471     }
    472 
    473     /**
    474      * Write to a protocol buffer output stream. Protocol buffer message definition is at
    475      * {@link com.android.server.wm.ConfigurationContainerProto}.
    476      *
    477      * @param proto    Stream to write the ConfigurationContainer object to.
    478      * @param fieldId  Field Id of the ConfigurationContainer as defined in the parent
    479      *                 message.
    480      * @param trim     If true, reduce amount of data written.
    481      * @hide
    482      */
    483     @CallSuper
    484     public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
    485         final long token = proto.start(fieldId);
    486         if (!trim || mHasOverrideConfiguration) {
    487             mOverrideConfiguration.writeToProto(proto, OVERRIDE_CONFIGURATION);
    488         }
    489         if (!trim) {
    490             mFullConfiguration.writeToProto(proto, FULL_CONFIGURATION);
    491             mMergedOverrideConfiguration.writeToProto(proto, MERGED_OVERRIDE_CONFIGURATION);
    492         }
    493         proto.end(token);
    494     }
    495 
    496     /**
    497      * Dumps the names of this container children in the input print writer indenting each
    498      * level with the input prefix.
    499      */
    500     public void dumpChildrenNames(PrintWriter pw, String prefix) {
    501         final String childPrefix = prefix + " ";
    502         pw.println(getName()
    503                 + " type=" + activityTypeToString(getActivityType())
    504                 + " mode=" + windowingModeToString(getWindowingMode()));
    505         for (int i = getChildCount() - 1; i >= 0; --i) {
    506             final E cc = getChildAt(i);
    507             pw.print(childPrefix + "#" + i + " ");
    508             cc.dumpChildrenNames(pw, childPrefix);
    509         }
    510     }
    511 
    512     String getName() {
    513         return toString();
    514     }
    515 
    516     boolean isAlwaysOnTop() {
    517         return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
    518     }
    519 
    520     abstract protected int getChildCount();
    521 
    522     abstract protected E getChildAt(int index);
    523 
    524     abstract protected ConfigurationContainer getParent();
    525 }
    526