Home | History | Annotate | Download | only in app
      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 android.app;
     18 
     19 import static android.app.ActivityThread.isSystem;
     20 import static android.app.WindowConfigurationProto.ACTIVITY_TYPE;
     21 import static android.app.WindowConfigurationProto.APP_BOUNDS;
     22 import static android.app.WindowConfigurationProto.WINDOWING_MODE;
     23 
     24 import android.annotation.IntDef;
     25 import android.annotation.NonNull;
     26 import android.annotation.TestApi;
     27 import android.content.res.Configuration;
     28 import android.graphics.Rect;
     29 import android.os.Parcel;
     30 import android.os.Parcelable;
     31 import android.util.proto.ProtoOutputStream;
     32 import android.view.DisplayInfo;
     33 
     34 /**
     35  * Class that contains windowing configuration/state for other objects that contain windows directly
     36  * or indirectly. E.g. Activities, Task, Displays, ...
     37  * The test class is {@link com.android.server.wm.WindowConfigurationTests} which must be kept
     38  * up-to-date and ran anytime changes are made to this class.
     39  * @hide
     40  */
     41 @TestApi
     42 public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {
     43     /**
     44      * bounds that can differ from app bounds, which may include things such as insets.
     45      *
     46      * TODO: Investigate combining with {@link mAppBounds}. Can the latter be a product of the
     47      * former?
     48      */
     49     private Rect mBounds = new Rect();
     50 
     51     /**
     52      * {@link android.graphics.Rect} defining app bounds. The dimensions override usages of
     53      * {@link DisplayInfo#appHeight} and {@link DisplayInfo#appWidth} and mirrors these values at
     54      * the display level. Lower levels can override these values to provide custom bounds to enforce
     55      * features such as a max aspect ratio.
     56      */
     57     private Rect mAppBounds;
     58 
     59     /** The current windowing mode of the configuration. */
     60     private @WindowingMode int mWindowingMode;
     61 
     62     /** Windowing mode is currently not defined. */
     63     public static final int WINDOWING_MODE_UNDEFINED = 0;
     64     /** Occupies the full area of the screen or the parent container. */
     65     public static final int WINDOWING_MODE_FULLSCREEN = 1;
     66     /** Always on-top (always visible). of other siblings in its parent container. */
     67     public static final int WINDOWING_MODE_PINNED = 2;
     68     /** The primary container driving the screen to be in split-screen mode. */
     69     public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3;
     70     /**
     71      * The containers adjacent to the {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} container in
     72      * split-screen mode.
     73      * NOTE: Containers launched with the windowing mode with APIs like
     74      * {@link ActivityOptions#setLaunchWindowingMode(int)} will be launched in
     75      * {@link #WINDOWING_MODE_FULLSCREEN} if the display isn't currently in split-screen windowing
     76      * mode
     77      * @see #WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY
     78      */
     79     public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4;
     80     /**
     81      * Alias for {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} that makes it clear that the usage
     82      * points for APIs like {@link ActivityOptions#setLaunchWindowingMode(int)} that the container
     83      * will launch into fullscreen or split-screen secondary depending on if the device is currently
     84      * in fullscreen mode or split-screen mode.
     85      */
     86     public static final int WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY =
     87             WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
     88     /** Can be freely resized within its parent container. */
     89     public static final int WINDOWING_MODE_FREEFORM = 5;
     90 
     91     /** @hide */
     92     @IntDef(prefix = { "WINDOWING_MODE_" }, value = {
     93             WINDOWING_MODE_UNDEFINED,
     94             WINDOWING_MODE_FULLSCREEN,
     95             WINDOWING_MODE_PINNED,
     96             WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
     97             WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
     98             WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY,
     99             WINDOWING_MODE_FREEFORM,
    100     })
    101     public @interface WindowingMode {}
    102 
    103     /** The current activity type of the configuration. */
    104     private @ActivityType int mActivityType;
    105 
    106     /** Activity type is currently not defined. */
    107     public static final int ACTIVITY_TYPE_UNDEFINED = 0;
    108     /** Standard activity type. Nothing special about the activity... */
    109     public static final int ACTIVITY_TYPE_STANDARD = 1;
    110     /** Home/Launcher activity type. */
    111     public static final int ACTIVITY_TYPE_HOME = 2;
    112     /** Recents/Overview activity type. There is only one activity with this type in the system. */
    113     public static final int ACTIVITY_TYPE_RECENTS = 3;
    114     /** Assistant activity type. */
    115     public static final int ACTIVITY_TYPE_ASSISTANT = 4;
    116 
    117     /** @hide */
    118     @IntDef(prefix = { "ACTIVITY_TYPE_" }, value = {
    119             ACTIVITY_TYPE_UNDEFINED,
    120             ACTIVITY_TYPE_STANDARD,
    121             ACTIVITY_TYPE_HOME,
    122             ACTIVITY_TYPE_RECENTS,
    123             ACTIVITY_TYPE_ASSISTANT,
    124     })
    125     public @interface ActivityType {}
    126 
    127     /** Bit that indicates that the {@link #mBounds} changed.
    128      * @hide */
    129     public static final int WINDOW_CONFIG_BOUNDS = 1 << 0;
    130     /** Bit that indicates that the {@link #mAppBounds} changed.
    131      * @hide */
    132     public static final int WINDOW_CONFIG_APP_BOUNDS = 1 << 1;
    133     /** Bit that indicates that the {@link #mWindowingMode} changed.
    134      * @hide */
    135     public static final int WINDOW_CONFIG_WINDOWING_MODE = 1 << 2;
    136     /** Bit that indicates that the {@link #mActivityType} changed.
    137      * @hide */
    138     public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 3;
    139 
    140     /** @hide */
    141     @IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = {
    142             WINDOW_CONFIG_BOUNDS,
    143             WINDOW_CONFIG_APP_BOUNDS,
    144             WINDOW_CONFIG_WINDOWING_MODE,
    145             WINDOW_CONFIG_ACTIVITY_TYPE
    146     })
    147     public @interface WindowConfig {}
    148 
    149     /** @hide */
    150     public static final int PINNED_WINDOWING_MODE_ELEVATION_IN_DIP = 5;
    151 
    152     public WindowConfiguration() {
    153         unset();
    154     }
    155 
    156     /** @hide */
    157     public WindowConfiguration(WindowConfiguration configuration) {
    158         setTo(configuration);
    159     }
    160 
    161     private WindowConfiguration(Parcel in) {
    162         readFromParcel(in);
    163     }
    164 
    165     @Override
    166     public void writeToParcel(Parcel dest, int flags) {
    167         dest.writeParcelable(mBounds, flags);
    168         dest.writeParcelable(mAppBounds, flags);
    169         dest.writeInt(mWindowingMode);
    170         dest.writeInt(mActivityType);
    171     }
    172 
    173     private void readFromParcel(Parcel source) {
    174         mBounds = source.readParcelable(Rect.class.getClassLoader());
    175         mAppBounds = source.readParcelable(Rect.class.getClassLoader());
    176         mWindowingMode = source.readInt();
    177         mActivityType = source.readInt();
    178     }
    179 
    180     @Override
    181     public int describeContents() {
    182         return 0;
    183     }
    184 
    185     /** @hide */
    186     public static final Creator<WindowConfiguration> CREATOR = new Creator<WindowConfiguration>() {
    187         @Override
    188         public WindowConfiguration createFromParcel(Parcel in) {
    189             return new WindowConfiguration(in);
    190         }
    191 
    192         @Override
    193         public WindowConfiguration[] newArray(int size) {
    194             return new WindowConfiguration[size];
    195         }
    196     };
    197 
    198     /**
    199      * Sets the bounds to the provided {@link Rect}.
    200      * @param rect the new bounds value.
    201      */
    202     public void setBounds(Rect rect) {
    203         if (rect == null) {
    204             mBounds.setEmpty();
    205             return;
    206         }
    207 
    208         mBounds.set(rect);
    209     }
    210 
    211     /**
    212      * Set {@link #mAppBounds} to the input Rect.
    213      * @param rect The rect value to set {@link #mAppBounds} to.
    214      * @see #getAppBounds()
    215      */
    216     public void setAppBounds(Rect rect) {
    217         if (rect == null) {
    218             mAppBounds = null;
    219             return;
    220         }
    221 
    222         setAppBounds(rect.left, rect.top, rect.right, rect.bottom);
    223     }
    224 
    225     /**
    226      * @see #setAppBounds(Rect)
    227      * @see #getAppBounds()
    228      * @hide
    229      */
    230     public void setAppBounds(int left, int top, int right, int bottom) {
    231         if (mAppBounds == null) {
    232             mAppBounds = new Rect();
    233         }
    234 
    235         mAppBounds.set(left, top, right, bottom);
    236     }
    237 
    238     /** @see #setAppBounds(Rect) */
    239     public Rect getAppBounds() {
    240         return mAppBounds;
    241     }
    242 
    243     /** @see #setBounds(Rect) */
    244     public Rect getBounds() {
    245         return mBounds;
    246     }
    247 
    248     public void setWindowingMode(@WindowingMode int windowingMode) {
    249         mWindowingMode = windowingMode;
    250     }
    251 
    252     @WindowingMode
    253     public int getWindowingMode() {
    254         return mWindowingMode;
    255     }
    256 
    257     public void setActivityType(@ActivityType int activityType) {
    258         if (mActivityType == activityType) {
    259             return;
    260         }
    261 
    262         // Error check within system server that we are not changing activity type which can be
    263         // dangerous. It is okay for things to change in the application process as it doesn't
    264         // affect how other things is the system is managed.
    265         if (isSystem()
    266                 && mActivityType != ACTIVITY_TYPE_UNDEFINED
    267                 && activityType != ACTIVITY_TYPE_UNDEFINED) {
    268             throw new IllegalStateException("Can't change activity type once set: " + this
    269                     + " activityType=" + activityTypeToString(activityType));
    270         }
    271         mActivityType = activityType;
    272     }
    273 
    274     @ActivityType
    275     public int getActivityType() {
    276         return mActivityType;
    277     }
    278 
    279     public void setTo(WindowConfiguration other) {
    280         setBounds(other.mBounds);
    281         setAppBounds(other.mAppBounds);
    282         setWindowingMode(other.mWindowingMode);
    283         setActivityType(other.mActivityType);
    284     }
    285 
    286     /** Set this object to completely undefined.
    287      * @hide */
    288     public void unset() {
    289         setToDefaults();
    290     }
    291 
    292     /** @hide */
    293     public void setToDefaults() {
    294         setAppBounds(null);
    295         setBounds(null);
    296         setWindowingMode(WINDOWING_MODE_UNDEFINED);
    297         setActivityType(ACTIVITY_TYPE_UNDEFINED);
    298     }
    299 
    300     /**
    301      * Copies the fields from delta into this Configuration object, keeping
    302      * track of which ones have changed. Any undefined fields in {@code delta}
    303      * are ignored and not copied in to the current Configuration.
    304      *
    305      * @return a bit mask of the changed fields, as per {@link #diff}
    306      * @hide
    307      */
    308     public @WindowConfig int updateFrom(@NonNull WindowConfiguration delta) {
    309         int changed = 0;
    310         // Only allow override if bounds is not empty
    311         if (!delta.mBounds.isEmpty() && !delta.mBounds.equals(mBounds)) {
    312             changed |= WINDOW_CONFIG_BOUNDS;
    313             setBounds(delta.mBounds);
    314         }
    315         if (delta.mAppBounds != null && !delta.mAppBounds.equals(mAppBounds)) {
    316             changed |= WINDOW_CONFIG_APP_BOUNDS;
    317             setAppBounds(delta.mAppBounds);
    318         }
    319         if (delta.mWindowingMode != WINDOWING_MODE_UNDEFINED
    320                 && mWindowingMode != delta.mWindowingMode) {
    321             changed |= WINDOW_CONFIG_WINDOWING_MODE;
    322             setWindowingMode(delta.mWindowingMode);
    323         }
    324         if (delta.mActivityType != ACTIVITY_TYPE_UNDEFINED
    325                 && mActivityType != delta.mActivityType) {
    326             changed |= WINDOW_CONFIG_ACTIVITY_TYPE;
    327             setActivityType(delta.mActivityType);
    328         }
    329         return changed;
    330     }
    331 
    332     /**
    333      * Return a bit mask of the differences between this Configuration object and the given one.
    334      * Does not change the values of either. Any undefined fields in <var>other</var> are ignored.
    335      * @param other The configuration to diff against.
    336      * @param compareUndefined If undefined values should be compared.
    337      * @return Returns a bit mask indicating which configuration
    338      * values has changed, containing any combination of {@link WindowConfig} flags.
    339      *
    340      * @see Configuration#diff(Configuration)
    341      * @hide
    342      */
    343     public @WindowConfig long diff(WindowConfiguration other, boolean compareUndefined) {
    344         long changes = 0;
    345 
    346         if (!mBounds.equals(other.mBounds)) {
    347             changes |= WINDOW_CONFIG_BOUNDS;
    348         }
    349 
    350         // Make sure that one of the values is not null and that they are not equal.
    351         if ((compareUndefined || other.mAppBounds != null)
    352                 && mAppBounds != other.mAppBounds
    353                 && (mAppBounds == null || !mAppBounds.equals(other.mAppBounds))) {
    354             changes |= WINDOW_CONFIG_APP_BOUNDS;
    355         }
    356 
    357         if ((compareUndefined || other.mWindowingMode != WINDOWING_MODE_UNDEFINED)
    358                 && mWindowingMode != other.mWindowingMode) {
    359             changes |= WINDOW_CONFIG_WINDOWING_MODE;
    360         }
    361 
    362         if ((compareUndefined || other.mActivityType != ACTIVITY_TYPE_UNDEFINED)
    363                 && mActivityType != other.mActivityType) {
    364             changes |= WINDOW_CONFIG_ACTIVITY_TYPE;
    365         }
    366 
    367         return changes;
    368     }
    369 
    370     @Override
    371     public int compareTo(WindowConfiguration that) {
    372         int n = 0;
    373         if (mAppBounds == null && that.mAppBounds != null) {
    374             return 1;
    375         } else if (mAppBounds != null && that.mAppBounds == null) {
    376             return -1;
    377         } else if (mAppBounds != null && that.mAppBounds != null) {
    378             n = mAppBounds.left - that.mAppBounds.left;
    379             if (n != 0) return n;
    380             n = mAppBounds.top - that.mAppBounds.top;
    381             if (n != 0) return n;
    382             n = mAppBounds.right - that.mAppBounds.right;
    383             if (n != 0) return n;
    384             n = mAppBounds.bottom - that.mAppBounds.bottom;
    385             if (n != 0) return n;
    386         }
    387 
    388         n = mBounds.left - that.mBounds.left;
    389         if (n != 0) return n;
    390         n = mBounds.top - that.mBounds.top;
    391         if (n != 0) return n;
    392         n = mBounds.right - that.mBounds.right;
    393         if (n != 0) return n;
    394         n = mBounds.bottom - that.mBounds.bottom;
    395         if (n != 0) return n;
    396 
    397         n = mWindowingMode - that.mWindowingMode;
    398         if (n != 0) return n;
    399         n = mActivityType - that.mActivityType;
    400         if (n != 0) return n;
    401 
    402         // if (n != 0) return n;
    403         return n;
    404     }
    405 
    406     /** @hide */
    407     @Override
    408     public boolean equals(Object that) {
    409         if (that == null) return false;
    410         if (that == this) return true;
    411         if (!(that instanceof WindowConfiguration)) {
    412             return false;
    413         }
    414         return this.compareTo((WindowConfiguration) that) == 0;
    415     }
    416 
    417     /** @hide */
    418     @Override
    419     public int hashCode() {
    420         int result = 0;
    421         if (mAppBounds != null) {
    422             result = 31 * result + mAppBounds.hashCode();
    423         }
    424         result = 31 * result + mBounds.hashCode();
    425 
    426         result = 31 * result + mWindowingMode;
    427         result = 31 * result + mActivityType;
    428         return result;
    429     }
    430 
    431     /** @hide */
    432     @Override
    433     public String toString() {
    434         return "{ mBounds=" + mBounds
    435                 + " mAppBounds=" + mAppBounds
    436                 + " mWindowingMode=" + windowingModeToString(mWindowingMode)
    437                 + " mActivityType=" + activityTypeToString(mActivityType) + "}";
    438     }
    439 
    440     /**
    441      * Write to a protocol buffer output stream.
    442      * Protocol buffer message definition at {@link android.app.WindowConfigurationProto}
    443      *
    444      * @param protoOutputStream Stream to write the WindowConfiguration object to.
    445      * @param fieldId           Field Id of the WindowConfiguration as defined in the parent message
    446      * @hide
    447      */
    448     public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
    449         final long token = protoOutputStream.start(fieldId);
    450         if (mAppBounds != null) {
    451             mAppBounds.writeToProto(protoOutputStream, APP_BOUNDS);
    452         }
    453         protoOutputStream.write(WINDOWING_MODE, mWindowingMode);
    454         protoOutputStream.write(ACTIVITY_TYPE, mActivityType);
    455         protoOutputStream.end(token);
    456     }
    457 
    458     /**
    459      * Returns true if the activities associated with this window configuration display a shadow
    460      * around their border.
    461      * @hide
    462      */
    463     public boolean hasWindowShadow() {
    464         return tasksAreFloating();
    465     }
    466 
    467     /**
    468      * Returns true if the activities associated with this window configuration display a decor
    469      * view.
    470      * @hide
    471      */
    472     public boolean hasWindowDecorCaption() {
    473         return mWindowingMode == WINDOWING_MODE_FREEFORM;
    474     }
    475 
    476     /**
    477      * Returns true if the tasks associated with this window configuration can be resized
    478      * independently of their parent container.
    479      * @hide
    480      */
    481     public boolean canResizeTask() {
    482         return mWindowingMode == WINDOWING_MODE_FREEFORM;
    483     }
    484 
    485     /** Returns true if the task bounds should persist across power cycles.
    486      * @hide */
    487     public boolean persistTaskBounds() {
    488         return mWindowingMode == WINDOWING_MODE_FREEFORM;
    489     }
    490 
    491     /**
    492      * Returns true if the tasks associated with this window configuration are floating.
    493      * Floating tasks are laid out differently as they are allowed to extend past the display bounds
    494      * without overscan insets.
    495      * @hide
    496      */
    497     public boolean tasksAreFloating() {
    498         return isFloating(mWindowingMode);
    499     }
    500 
    501     /**
    502      * Returns true if the windowingMode represents a floating window.
    503      * @hide
    504      */
    505     public static boolean isFloating(int windowingMode) {
    506         return windowingMode == WINDOWING_MODE_FREEFORM || windowingMode == WINDOWING_MODE_PINNED;
    507     }
    508 
    509     /**
    510      * Returns true if the windows associated with this window configuration can receive input keys.
    511      * @hide
    512      */
    513     public boolean canReceiveKeys() {
    514         return mWindowingMode != WINDOWING_MODE_PINNED;
    515     }
    516 
    517     /**
    518      * Returns true if the container associated with this window configuration is always-on-top of
    519      * its siblings.
    520      * @hide
    521      */
    522     public boolean isAlwaysOnTop() {
    523         return mWindowingMode == WINDOWING_MODE_PINNED;
    524     }
    525 
    526     /**
    527      * Returns true if any visible windows belonging to apps with this window configuration should
    528      * be kept on screen when the app is killed due to something like the low memory killer.
    529      * @hide
    530      */
    531     public boolean keepVisibleDeadAppWindowOnScreen() {
    532         return mWindowingMode != WINDOWING_MODE_PINNED;
    533     }
    534 
    535     /**
    536      * Returns true if the backdrop on the client side should match the frame of the window.
    537      * Returns false, if the backdrop should be fullscreen.
    538      * @hide
    539      */
    540     public boolean useWindowFrameForBackdrop() {
    541         return mWindowingMode == WINDOWING_MODE_FREEFORM || mWindowingMode == WINDOWING_MODE_PINNED;
    542     }
    543 
    544     /**
    545      * Returns true if this container may be scaled without resizing, and windows within may need
    546      * to be configured as such.
    547      * @hide
    548      */
    549     public boolean windowsAreScaleable() {
    550         return mWindowingMode == WINDOWING_MODE_PINNED;
    551     }
    552 
    553     /**
    554      * Returns true if windows in this container should be given move animations by default.
    555      * @hide
    556      */
    557     public boolean hasMovementAnimations() {
    558         return mWindowingMode != WINDOWING_MODE_PINNED;
    559     }
    560 
    561     /**
    562      * Returns true if this container can be put in either
    563      * {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
    564      * {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on its current state.
    565      * @hide
    566      */
    567     public boolean supportSplitScreenWindowingMode() {
    568         return supportSplitScreenWindowingMode(mActivityType);
    569     }
    570 
    571     /** @hide */
    572     public static boolean supportSplitScreenWindowingMode(int activityType) {
    573         return activityType != ACTIVITY_TYPE_ASSISTANT;
    574     }
    575 
    576     /** @hide */
    577     public static String windowingModeToString(@WindowingMode int windowingMode) {
    578         switch (windowingMode) {
    579             case WINDOWING_MODE_UNDEFINED: return "undefined";
    580             case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
    581             case WINDOWING_MODE_PINNED: return "pinned";
    582             case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return "split-screen-primary";
    583             case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return "split-screen-secondary";
    584             case WINDOWING_MODE_FREEFORM: return "freeform";
    585         }
    586         return String.valueOf(windowingMode);
    587     }
    588 
    589     /** @hide */
    590     public static String activityTypeToString(@ActivityType int applicationType) {
    591         switch (applicationType) {
    592             case ACTIVITY_TYPE_UNDEFINED: return "undefined";
    593             case ACTIVITY_TYPE_STANDARD: return "standard";
    594             case ACTIVITY_TYPE_HOME: return "home";
    595             case ACTIVITY_TYPE_RECENTS: return "recents";
    596             case ACTIVITY_TYPE_ASSISTANT: return "assistant";
    597         }
    598         return String.valueOf(applicationType);
    599     }
    600 }
    601