Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2018 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.view;
     18 
     19 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
     20 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
     21 import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
     22 import static android.view.WindowInsets.Type.SIZE;
     23 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
     24 import static android.view.WindowInsets.Type.indexOf;
     25 
     26 import android.annotation.IntDef;
     27 import android.annotation.Nullable;
     28 import android.graphics.Insets;
     29 import android.graphics.Rect;
     30 import android.os.Parcel;
     31 import android.os.Parcelable;
     32 import android.util.ArrayMap;
     33 import android.util.ArraySet;
     34 import android.util.SparseIntArray;
     35 import android.view.WindowInsets.Type;
     36 import android.view.WindowInsets.Type.InsetType;
     37 import android.view.WindowManager.LayoutParams;
     38 
     39 import java.io.PrintWriter;
     40 import java.lang.annotation.Retention;
     41 import java.lang.annotation.RetentionPolicy;
     42 import java.util.Objects;
     43 
     44 /**
     45  * Holder for state of system windows that cause window insets for all other windows in the system.
     46  * @hide
     47  */
     48 public class InsetsState implements Parcelable {
     49 
     50     /**
     51      * Internal representation of inset source types. This is different from the public API in
     52      * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows
     53      * at the same time.
     54      */
     55     @Retention(RetentionPolicy.SOURCE)
     56     @IntDef(prefix = "TYPE", value = {
     57             TYPE_TOP_BAR,
     58             TYPE_SIDE_BAR_1,
     59             TYPE_SIDE_BAR_2,
     60             TYPE_SIDE_BAR_3,
     61             TYPE_TOP_GESTURES,
     62             TYPE_BOTTOM_GESTURES,
     63             TYPE_LEFT_GESTURES,
     64             TYPE_RIGHT_GESTURES,
     65             TYPE_TOP_TAPPABLE_ELEMENT,
     66             TYPE_BOTTOM_TAPPABLE_ELEMENT,
     67             TYPE_IME
     68     })
     69     public @interface InternalInsetType {}
     70 
     71     static final int FIRST_TYPE = 0;
     72 
     73     /** Top bar. Can be status bar or caption in freeform windowing mode. */
     74     public static final int TYPE_TOP_BAR = FIRST_TYPE;
     75 
     76     /**
     77      * Up to 3 side bars that appear on left/right/bottom. On phones there is only one side bar
     78      * (the navigation bar, see {@link #TYPE_NAVIGATION_BAR}), but other form factors might have
     79      * multiple, like Android Auto.
     80      */
     81     public static final int TYPE_SIDE_BAR_1 = 1;
     82     public static final int TYPE_SIDE_BAR_2 = 2;
     83     public static final int TYPE_SIDE_BAR_3 = 3;
     84 
     85     public static final int TYPE_TOP_GESTURES = 4;
     86     public static final int TYPE_BOTTOM_GESTURES = 5;
     87     public static final int TYPE_LEFT_GESTURES = 6;
     88     public static final int TYPE_RIGHT_GESTURES = 7;
     89     public static final int TYPE_TOP_TAPPABLE_ELEMENT = 8;
     90     public static final int TYPE_BOTTOM_TAPPABLE_ELEMENT = 9;
     91 
     92     /** Input method window. */
     93     public static final int TYPE_IME = 10;
     94 
     95     static final int LAST_TYPE = TYPE_IME;
     96 
     97     // Derived types
     98 
     99     /** First side bar is navigation bar. */
    100     public static final int TYPE_NAVIGATION_BAR = TYPE_SIDE_BAR_1;
    101 
    102     /** A shelf is the same as the navigation bar. */
    103     public static final int TYPE_SHELF = TYPE_NAVIGATION_BAR;
    104 
    105     @Retention(RetentionPolicy.SOURCE)
    106     @IntDef(prefix = "INSET_SIDE", value = {
    107             INSET_SIDE_LEFT,
    108             INSET_SIDE_TOP,
    109             INSET_SIDE_RIGHT,
    110             INSET_SIDE_BOTTOM,
    111             INSET_SIDE_UNKNWON
    112     })
    113     public @interface InsetSide {}
    114     static final int INSET_SIDE_LEFT = 0;
    115     static final int INSET_SIDE_TOP = 1;
    116     static final int INSET_SIDE_RIGHT = 2;
    117     static final int INSET_SIDE_BOTTOM = 3;
    118     static final int INSET_SIDE_UNKNWON = 4;
    119 
    120     private final ArrayMap<Integer, InsetsSource> mSources = new ArrayMap<>();
    121 
    122     /**
    123      * The frame of the display these sources are relative to.
    124      */
    125     private final Rect mDisplayFrame = new Rect();
    126 
    127     public InsetsState() {
    128     }
    129 
    130     public InsetsState(InsetsState copy) {
    131         set(copy);
    132     }
    133 
    134     public InsetsState(InsetsState copy, boolean copySources) {
    135         set(copy, copySources);
    136     }
    137 
    138     /**
    139      * Calculates {@link WindowInsets} based on the current source configuration.
    140      *
    141      * @param frame The frame to calculate the insets relative to.
    142      * @return The calculated insets.
    143      */
    144     public WindowInsets calculateInsets(Rect frame, boolean isScreenRound,
    145             boolean alwaysConsumeSystemBars, DisplayCutout cutout,
    146             @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets,
    147             int legacySoftInputMode, @Nullable @InsetSide SparseIntArray typeSideMap) {
    148         Insets[] typeInsetsMap = new Insets[Type.SIZE];
    149         Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
    150         boolean[] typeVisibilityMap = new boolean[SIZE];
    151         final Rect relativeFrame = new Rect(frame);
    152         final Rect relativeFrameMax = new Rect(frame);
    153         if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
    154                 && legacyContentInsets != null && legacyStableInsets != null) {
    155             WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
    156             WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
    157         }
    158         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
    159             InsetsSource source = mSources.get(type);
    160             if (source == null) {
    161                 continue;
    162             }
    163 
    164             boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
    165                     && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR);
    166             boolean skipIme = source.getType() == TYPE_IME
    167                     && (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0;
    168             boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
    169                     && (toPublicType(type) & Type.compatSystemInsets()) != 0;
    170             if (skipSystemBars || skipIme || skipLegacyTypes) {
    171                 typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
    172                 continue;
    173             }
    174 
    175             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
    176                     typeSideMap, typeVisibilityMap);
    177 
    178             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
    179             // target.
    180             if (source.getType() != TYPE_IME) {
    181                 processSource(source, relativeFrameMax, true /* ignoreVisibility */,
    182                         typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */);
    183             }
    184         }
    185         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
    186                 alwaysConsumeSystemBars, cutout);
    187     }
    188 
    189     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
    190             Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap,
    191             @Nullable boolean[] typeVisibilityMap) {
    192         Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
    193 
    194         int type = toPublicType(source.getType());
    195         processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
    196                 insets, type);
    197 
    198         if (type == MANDATORY_SYSTEM_GESTURES) {
    199             // Mandatory system gestures are also system gestures.
    200             // TODO: find a way to express this more generally. One option would be to define
    201             //       Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
    202             //       ability to set systemGestureInsets() independently from
    203             //       mandatorySystemGestureInsets() in the Builder.
    204             processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
    205                     insets, SYSTEM_GESTURES);
    206         }
    207     }
    208 
    209     private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
    210             @InsetSide @Nullable SparseIntArray typeSideMap,
    211             @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
    212         int index = indexOf(type);
    213         Insets existing = typeInsetsMap[index];
    214         if (existing == null) {
    215             typeInsetsMap[index] = insets;
    216         } else {
    217             typeInsetsMap[index] = Insets.max(existing, insets);
    218         }
    219 
    220         if (typeVisibilityMap != null) {
    221             typeVisibilityMap[index] = source.isVisible();
    222         }
    223 
    224         if (typeSideMap != null && !Insets.NONE.equals(insets)) {
    225             @InsetSide int insetSide = getInsetSide(insets);
    226             if (insetSide != INSET_SIDE_UNKNWON) {
    227                 typeSideMap.put(source.getType(), getInsetSide(insets));
    228             }
    229         }
    230     }
    231 
    232     /**
    233      * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
    234      * is set in order that this method returns a meaningful result.
    235      */
    236     private @InsetSide int getInsetSide(Insets insets) {
    237         if (insets.left != 0) {
    238             return INSET_SIDE_LEFT;
    239         }
    240         if (insets.top != 0) {
    241             return INSET_SIDE_TOP;
    242         }
    243         if (insets.right != 0) {
    244             return INSET_SIDE_RIGHT;
    245         }
    246         if (insets.bottom != 0) {
    247             return INSET_SIDE_BOTTOM;
    248         }
    249         return INSET_SIDE_UNKNWON;
    250     }
    251 
    252     public InsetsSource getSource(@InternalInsetType int type) {
    253         return mSources.computeIfAbsent(type, InsetsSource::new);
    254     }
    255 
    256     public void setDisplayFrame(Rect frame) {
    257         mDisplayFrame.set(frame);
    258     }
    259 
    260     public Rect getDisplayFrame() {
    261         return mDisplayFrame;
    262     }
    263 
    264     /**
    265      * Modifies the state of this class to exclude a certain type to make it ready for dispatching
    266      * to the client.
    267      *
    268      * @param type The {@link InternalInsetType} of the source to remove
    269      */
    270     public void removeSource(int type) {
    271         mSources.remove(type);
    272     }
    273 
    274     public void set(InsetsState other) {
    275         set(other, false /* copySources */);
    276     }
    277 
    278     public void set(InsetsState other, boolean copySources) {
    279         mDisplayFrame.set(other.mDisplayFrame);
    280         mSources.clear();
    281         if (copySources) {
    282             for (int i = 0; i < other.mSources.size(); i++) {
    283                 InsetsSource source = other.mSources.valueAt(i);
    284                 mSources.put(source.getType(), new InsetsSource(source));
    285             }
    286         } else {
    287             mSources.putAll(other.mSources);
    288         }
    289     }
    290 
    291     public void addSource(InsetsSource source) {
    292         mSources.put(source.getType(), source);
    293     }
    294 
    295     public int getSourcesCount() {
    296         return mSources.size();
    297     }
    298 
    299     public InsetsSource sourceAt(int index) {
    300         return mSources.valueAt(index);
    301     }
    302 
    303     public static @InternalInsetType ArraySet<Integer> toInternalType(@InsetType int insetTypes) {
    304         final ArraySet<Integer> result = new ArraySet<>();
    305         if ((insetTypes & Type.TOP_BAR) != 0) {
    306             result.add(TYPE_TOP_BAR);
    307         }
    308         if ((insetTypes & Type.SIDE_BARS) != 0) {
    309             result.add(TYPE_SIDE_BAR_1);
    310             result.add(TYPE_SIDE_BAR_2);
    311             result.add(TYPE_SIDE_BAR_3);
    312         }
    313         if ((insetTypes & Type.IME) != 0) {
    314             result.add(TYPE_IME);
    315         }
    316         return result;
    317     }
    318 
    319     static @InsetType int toPublicType(@InternalInsetType int type) {
    320         switch (type) {
    321             case TYPE_TOP_BAR:
    322                 return Type.TOP_BAR;
    323             case TYPE_SIDE_BAR_1:
    324             case TYPE_SIDE_BAR_2:
    325             case TYPE_SIDE_BAR_3:
    326                 return Type.SIDE_BARS;
    327             case TYPE_IME:
    328                 return Type.IME;
    329             case TYPE_TOP_GESTURES:
    330             case TYPE_BOTTOM_GESTURES:
    331                 return Type.MANDATORY_SYSTEM_GESTURES;
    332             case TYPE_LEFT_GESTURES:
    333             case TYPE_RIGHT_GESTURES:
    334                 return Type.SYSTEM_GESTURES;
    335             case TYPE_TOP_TAPPABLE_ELEMENT:
    336             case TYPE_BOTTOM_TAPPABLE_ELEMENT:
    337                 return Type.TAPPABLE_ELEMENT;
    338             default:
    339                 throw new IllegalArgumentException("Unknown type: " + type);
    340         }
    341     }
    342 
    343     public static boolean getDefaultVisibility(@InsetType int type) {
    344         switch (type) {
    345             case TYPE_TOP_BAR:
    346             case TYPE_SIDE_BAR_1:
    347             case TYPE_SIDE_BAR_2:
    348             case TYPE_SIDE_BAR_3:
    349                 return true;
    350             case TYPE_IME:
    351                 return false;
    352             default:
    353                 return true;
    354         }
    355     }
    356 
    357     public void dump(String prefix, PrintWriter pw) {
    358         pw.println(prefix + "InsetsState");
    359         for (int i = mSources.size() - 1; i >= 0; i--) {
    360             mSources.valueAt(i).dump(prefix + "  ", pw);
    361         }
    362     }
    363 
    364     public static String typeToString(int type) {
    365         switch (type) {
    366             case TYPE_TOP_BAR:
    367                 return "TYPE_TOP_BAR";
    368             case TYPE_SIDE_BAR_1:
    369                 return "TYPE_SIDE_BAR_1";
    370             case TYPE_SIDE_BAR_2:
    371                 return "TYPE_SIDE_BAR_2";
    372             case TYPE_SIDE_BAR_3:
    373                 return "TYPE_SIDE_BAR_3";
    374             case TYPE_TOP_GESTURES:
    375                 return "TYPE_TOP_GESTURES";
    376             case TYPE_BOTTOM_GESTURES:
    377                 return "TYPE_BOTTOM_GESTURES";
    378             case TYPE_LEFT_GESTURES:
    379                 return "TYPE_LEFT_GESTURES";
    380             case TYPE_RIGHT_GESTURES:
    381                 return "TYPE_RIGHT_GESTURES";
    382             case TYPE_TOP_TAPPABLE_ELEMENT:
    383                 return "TYPE_TOP_TAPPABLE_ELEMENT";
    384             case TYPE_BOTTOM_TAPPABLE_ELEMENT:
    385                 return "TYPE_BOTTOM_TAPPABLE_ELEMENT";
    386             default:
    387                 return "TYPE_UNKNOWN_" + type;
    388         }
    389     }
    390 
    391     @Override
    392     public boolean equals(Object o) {
    393         if (this == o) { return true; }
    394         if (o == null || getClass() != o.getClass()) { return false; }
    395 
    396         InsetsState state = (InsetsState) o;
    397 
    398         if (!mDisplayFrame.equals(state.mDisplayFrame)) {
    399             return false;
    400         }
    401         if (mSources.size() != state.mSources.size()) {
    402             return false;
    403         }
    404         for (int i = mSources.size() - 1; i >= 0; i--) {
    405             InsetsSource source = mSources.valueAt(i);
    406             InsetsSource otherSource = state.mSources.get(source.getType());
    407             if (otherSource == null) {
    408                 return false;
    409             }
    410             if (!otherSource.equals(source)) {
    411                 return false;
    412             }
    413         }
    414         return true;
    415     }
    416 
    417     @Override
    418     public int hashCode() {
    419         return Objects.hash(mDisplayFrame, mSources);
    420     }
    421 
    422     public InsetsState(Parcel in) {
    423         readFromParcel(in);
    424     }
    425 
    426     @Override
    427     public int describeContents() {
    428         return 0;
    429     }
    430 
    431     @Override
    432     public void writeToParcel(Parcel dest, int flags) {
    433         dest.writeParcelable(mDisplayFrame, flags);
    434         dest.writeInt(mSources.size());
    435         for (int i = 0; i < mSources.size(); i++) {
    436             dest.writeParcelable(mSources.valueAt(i), flags);
    437         }
    438     }
    439 
    440     public static final @android.annotation.NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
    441 
    442         public InsetsState createFromParcel(Parcel in) {
    443             return new InsetsState(in);
    444         }
    445 
    446         public InsetsState[] newArray(int size) {
    447             return new InsetsState[size];
    448         }
    449     };
    450 
    451     public void readFromParcel(Parcel in) {
    452         mSources.clear();
    453         mDisplayFrame.set(in.readParcelable(null /* loader */));
    454         final int size = in.readInt();
    455         for (int i = 0; i < size; i++) {
    456             final InsetsSource source = in.readParcelable(null /* loader */);
    457             mSources.put(source.getType(), source);
    458         }
    459     }
    460 }
    461 
    462