• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.wm.shell.common;
18 
19 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
20 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
21 import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
22 import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
23 import static android.os.Process.SYSTEM_UID;
24 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
25 import static android.util.RotationUtils.rotateBounds;
26 import static android.util.RotationUtils.rotateInsets;
27 import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
28 import static android.view.Surface.ROTATION_0;
29 import static android.view.Surface.ROTATION_270;
30 import static android.view.Surface.ROTATION_90;
31 
32 import android.annotation.IntDef;
33 import android.annotation.NonNull;
34 import android.content.ContentResolver;
35 import android.content.Context;
36 import android.content.res.Resources;
37 import android.graphics.Insets;
38 import android.graphics.Rect;
39 import android.os.SystemProperties;
40 import android.provider.Settings;
41 import android.util.DisplayMetrics;
42 import android.util.Size;
43 import android.view.Display;
44 import android.view.DisplayCutout;
45 import android.view.DisplayInfo;
46 import android.view.Gravity;
47 import android.view.Surface;
48 
49 import com.android.internal.R;
50 
51 import java.lang.annotation.Retention;
52 import java.lang.annotation.RetentionPolicy;
53 import java.util.Objects;
54 
55 /**
56  * Contains information about the layout-properties of a display. This refers to internal layout
57  * like insets/cutout/rotation. In general, this can be thought of as the shell analog to
58  * DisplayPolicy.
59  */
60 public class DisplayLayout {
61     @IntDef(prefix = { "NAV_BAR_" }, value = {
62             NAV_BAR_LEFT,
63             NAV_BAR_RIGHT,
64             NAV_BAR_BOTTOM,
65     })
66     @Retention(RetentionPolicy.SOURCE)
67     public @interface NavBarPosition {}
68 
69     // Navigation bar position values
70     public static final int NAV_BAR_LEFT = 1 << 0;
71     public static final int NAV_BAR_RIGHT = 1 << 1;
72     public static final int NAV_BAR_BOTTOM = 1 << 2;
73 
74     private int mUiMode;
75     private int mWidth;
76     private int mHeight;
77     private DisplayCutout mCutout;
78     private int mRotation;
79     private int mDensityDpi;
80     private final Rect mNonDecorInsets = new Rect();
81     private final Rect mStableInsets = new Rect();
82     private boolean mHasNavigationBar = false;
83     private boolean mHasStatusBar = false;
84     private int mNavBarFrameHeight = 0;
85 
86     @Override
equals(Object o)87     public boolean equals(Object o) {
88         if (this == o) return true;
89         if (!(o instanceof DisplayLayout)) return false;
90         final DisplayLayout other = (DisplayLayout) o;
91         return mUiMode == other.mUiMode
92                 && mWidth == other.mWidth
93                 && mHeight == other.mHeight
94                 && Objects.equals(mCutout, other.mCutout)
95                 && mRotation == other.mRotation
96                 && mDensityDpi == other.mDensityDpi
97                 && Objects.equals(mNonDecorInsets, other.mNonDecorInsets)
98                 && Objects.equals(mStableInsets, other.mStableInsets)
99                 && mHasNavigationBar == other.mHasNavigationBar
100                 && mHasStatusBar == other.mHasStatusBar
101                 && mNavBarFrameHeight == other.mNavBarFrameHeight;
102     }
103 
104     @Override
hashCode()105     public int hashCode() {
106         return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi,
107                 mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
108                 mNavBarFrameHeight);
109     }
110 
111     /**
112      * Create empty layout.
113      */
DisplayLayout()114     public DisplayLayout() {
115     }
116 
117     /**
118      * Construct a custom display layout using a DisplayInfo.
119      * @param info
120      * @param res
121      */
DisplayLayout(DisplayInfo info, Resources res, boolean hasNavigationBar, boolean hasStatusBar)122     public DisplayLayout(DisplayInfo info, Resources res, boolean hasNavigationBar,
123             boolean hasStatusBar) {
124         init(info, res, hasNavigationBar, hasStatusBar);
125     }
126 
127     /**
128      * Construct a display layout based on a live display.
129      * @param context Used for resources.
130      */
DisplayLayout(@onNull Context context, @NonNull Display rawDisplay)131     public DisplayLayout(@NonNull Context context, @NonNull Display rawDisplay) {
132         final int displayId = rawDisplay.getDisplayId();
133         DisplayInfo info = new DisplayInfo();
134         rawDisplay.getDisplayInfo(info);
135         init(info, context.getResources(), hasNavigationBar(info, context, displayId),
136                 hasStatusBar(displayId));
137     }
138 
DisplayLayout(DisplayLayout dl)139     public DisplayLayout(DisplayLayout dl) {
140         set(dl);
141     }
142 
143     /** sets this DisplayLayout to a copy of another on. */
set(DisplayLayout dl)144     public void set(DisplayLayout dl) {
145         mUiMode = dl.mUiMode;
146         mWidth = dl.mWidth;
147         mHeight = dl.mHeight;
148         mCutout = dl.mCutout;
149         mRotation = dl.mRotation;
150         mDensityDpi = dl.mDensityDpi;
151         mHasNavigationBar = dl.mHasNavigationBar;
152         mHasStatusBar = dl.mHasStatusBar;
153         mNavBarFrameHeight = dl.mNavBarFrameHeight;
154         mNonDecorInsets.set(dl.mNonDecorInsets);
155         mStableInsets.set(dl.mStableInsets);
156     }
157 
init(DisplayInfo info, Resources res, boolean hasNavigationBar, boolean hasStatusBar)158     private void init(DisplayInfo info, Resources res, boolean hasNavigationBar,
159             boolean hasStatusBar) {
160         mUiMode = res.getConfiguration().uiMode;
161         mWidth = info.logicalWidth;
162         mHeight = info.logicalHeight;
163         mRotation = info.rotation;
164         mCutout = info.displayCutout;
165         mDensityDpi = info.logicalDensityDpi;
166         mHasNavigationBar = hasNavigationBar;
167         mHasStatusBar = hasStatusBar;
168         recalcInsets(res);
169     }
170 
recalcInsets(Resources res)171     private void recalcInsets(Resources res) {
172         computeNonDecorInsets(res, mRotation, mWidth, mHeight, mCutout, mUiMode, mNonDecorInsets,
173                 mHasNavigationBar);
174         mStableInsets.set(mNonDecorInsets);
175         if (mHasStatusBar) {
176             convertNonDecorInsetsToStableInsets(res, mStableInsets, mWidth, mHeight, mHasStatusBar);
177         }
178         mNavBarFrameHeight = getNavigationBarFrameHeight(res, mWidth > mHeight);
179     }
180 
181     /**
182      * Apply a rotation to this layout and its parameters.
183      * @param res
184      * @param targetRotation
185      */
rotateTo(Resources res, @Surface.Rotation int targetRotation)186     public void rotateTo(Resources res, @Surface.Rotation int targetRotation) {
187         final int rotationDelta = (targetRotation - mRotation + 4) % 4;
188         final boolean changeOrient = (rotationDelta % 2) != 0;
189 
190         final int origWidth = mWidth;
191         final int origHeight = mHeight;
192 
193         mRotation = targetRotation;
194         if (changeOrient) {
195             mWidth = origHeight;
196             mHeight = origWidth;
197         }
198 
199         if (mCutout != null && !mCutout.isEmpty()) {
200             mCutout = calculateDisplayCutoutForRotation(mCutout, rotationDelta, origWidth,
201                     origHeight);
202         }
203 
204         recalcInsets(res);
205     }
206 
207     /** Get this layout's non-decor insets. */
nonDecorInsets()208     public Rect nonDecorInsets() {
209         return mNonDecorInsets;
210     }
211 
212     /** Get this layout's stable insets. */
stableInsets()213     public Rect stableInsets() {
214         return mStableInsets;
215     }
216 
217     /** Get this layout's width. */
width()218     public int width() {
219         return mWidth;
220     }
221 
222     /** Get this layout's height. */
height()223     public int height() {
224         return mHeight;
225     }
226 
227     /** Get this layout's display rotation. */
rotation()228     public int rotation() {
229         return mRotation;
230     }
231 
232     /** Get this layout's display density. */
densityDpi()233     public int densityDpi() {
234         return mDensityDpi;
235     }
236 
237     /** Get the density scale for the display. */
density()238     public float density() {
239         return mDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
240     }
241 
242     /** Get whether this layout is landscape. */
isLandscape()243     public boolean isLandscape() {
244         return mWidth > mHeight;
245     }
246 
247     /** Get the navbar frame height (used by ime). */
navBarFrameHeight()248     public int navBarFrameHeight() {
249         return mNavBarFrameHeight;
250     }
251 
252     /** Gets the orientation of this layout */
getOrientation()253     public int getOrientation() {
254         return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
255     }
256 
257     /** Gets the calculated stable-bounds for this layout */
getStableBounds(Rect outBounds)258     public void getStableBounds(Rect outBounds) {
259         outBounds.set(0, 0, mWidth, mHeight);
260         outBounds.inset(mStableInsets);
261     }
262 
263     /**
264      * Gets navigation bar position for this layout
265      * @return Navigation bar position for this layout.
266      */
getNavigationBarPosition(Resources res)267     public @NavBarPosition int getNavigationBarPosition(Resources res) {
268         return navigationBarPosition(res, mWidth, mHeight, mRotation);
269     }
270 
271     /**
272      * Calculates the stable insets if we already have the non-decor insets.
273      */
convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets, int displayWidth, int displayHeight, boolean hasStatusBar)274     private static void convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets,
275             int displayWidth, int displayHeight, boolean hasStatusBar) {
276         if (!hasStatusBar) {
277             return;
278         }
279         int statusBarHeight = getStatusBarHeight(displayWidth > displayHeight, res);
280         inOutInsets.top = Math.max(inOutInsets.top, statusBarHeight);
281     }
282 
283     /**
284      * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
285      * bar or button bar.
286      *
287      * @param displayRotation the current display rotation
288      * @param displayWidth the current display width
289      * @param displayHeight the current display height
290      * @param displayCutout the current display cutout
291      * @param outInsets the insets to return
292      */
computeNonDecorInsets(Resources res, int displayRotation, int displayWidth, int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets, boolean hasNavigationBar)293     static void computeNonDecorInsets(Resources res, int displayRotation, int displayWidth,
294             int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets,
295             boolean hasNavigationBar) {
296         outInsets.setEmpty();
297 
298         // Only navigation bar
299         if (hasNavigationBar) {
300             int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
301             int navBarSize =
302                     getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
303             if (position == NAV_BAR_BOTTOM) {
304                 outInsets.bottom = navBarSize;
305             } else if (position == NAV_BAR_RIGHT) {
306                 outInsets.right = navBarSize;
307             } else if (position == NAV_BAR_LEFT) {
308                 outInsets.left = navBarSize;
309             }
310         }
311 
312         if (displayCutout != null) {
313             outInsets.left += displayCutout.getSafeInsetLeft();
314             outInsets.top += displayCutout.getSafeInsetTop();
315             outInsets.right += displayCutout.getSafeInsetRight();
316             outInsets.bottom += displayCutout.getSafeInsetBottom();
317         }
318     }
319 
320     /**
321      * Calculates the stable insets without running a layout.
322      *
323      * @param displayRotation the current display rotation
324      * @param displayWidth the current display width
325      * @param displayHeight the current display height
326      * @param displayCutout the current display cutout
327      * @param outInsets the insets to return
328      */
computeStableInsets(Resources res, int displayRotation, int displayWidth, int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets, boolean hasNavigationBar, boolean hasStatusBar)329     static void computeStableInsets(Resources res, int displayRotation, int displayWidth,
330             int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets,
331             boolean hasNavigationBar, boolean hasStatusBar) {
332         outInsets.setEmpty();
333 
334         // Navigation bar and status bar.
335         computeNonDecorInsets(res, displayRotation, displayWidth, displayHeight, displayCutout,
336                 uiMode, outInsets, hasNavigationBar);
337         convertNonDecorInsetsToStableInsets(res, outInsets, displayWidth, displayHeight,
338                 hasStatusBar);
339     }
340 
341     /** Retrieve the statusbar height from resources. */
getStatusBarHeight(boolean landscape, Resources res)342     static int getStatusBarHeight(boolean landscape, Resources res) {
343         return landscape ? res.getDimensionPixelSize(
344                 com.android.internal.R.dimen.status_bar_height_landscape)
345                 : res.getDimensionPixelSize(
346                         com.android.internal.R.dimen.status_bar_height_portrait);
347     }
348 
349     /** Calculate the DisplayCutout for a particular display size/rotation. */
calculateDisplayCutoutForRotation( DisplayCutout cutout, int rotation, int displayWidth, int displayHeight)350     public static DisplayCutout calculateDisplayCutoutForRotation(
351             DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
352         if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
353             return null;
354         }
355         if (rotation == ROTATION_0) {
356             return computeSafeInsets(cutout, displayWidth, displayHeight);
357         }
358         final Insets waterfallInsets = rotateInsets(cutout.getWaterfallInsets(), rotation);
359         final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
360         Rect[] cutoutRects = cutout.getBoundingRectsAll();
361         final Rect[] newBounds = new Rect[cutoutRects.length];
362         final Rect displayBounds = new Rect(0, 0, displayWidth, displayHeight);
363         for (int i = 0; i < cutoutRects.length; ++i) {
364             final Rect rect = new Rect(cutoutRects[i]);
365             if (!rect.isEmpty()) {
366                 rotateBounds(rect, displayBounds, rotation);
367             }
368             newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
369         }
370         final DisplayCutout.CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
371         final DisplayCutout.CutoutPathParserInfo newInfo = new DisplayCutout.CutoutPathParserInfo(
372                 info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
373                 info.getCutoutSpec(), rotation, info.getScale());
374         return computeSafeInsets(
375                 DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
376                 rotated ? displayHeight : displayWidth,
377                 rotated ? displayWidth : displayHeight);
378     }
379 
getBoundIndexFromRotation(int index, int rotation)380     private static int getBoundIndexFromRotation(int index, int rotation) {
381         return (index - rotation) < 0
382                 ? index - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH
383                 : index - rotation;
384     }
385 
386     /** Calculate safe insets. */
computeSafeInsets(DisplayCutout inner, int displayWidth, int displayHeight)387     public static DisplayCutout computeSafeInsets(DisplayCutout inner,
388             int displayWidth, int displayHeight) {
389         if (inner == DisplayCutout.NO_CUTOUT) {
390             return null;
391         }
392 
393         final Size displaySize = new Size(displayWidth, displayHeight);
394         final Rect safeInsets = computeSafeInsets(displaySize, inner);
395         return inner.replaceSafeInsets(safeInsets);
396     }
397 
computeSafeInsets( Size displaySize, DisplayCutout cutout)398     private static Rect computeSafeInsets(
399             Size displaySize, DisplayCutout cutout) {
400         if (displaySize.getWidth() == displaySize.getHeight()) {
401             throw new UnsupportedOperationException("not implemented: display=" + displaySize
402                     + " cutout=" + cutout);
403         }
404 
405         int leftInset = Math.max(cutout.getWaterfallInsets().left,
406                 findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT));
407         int topInset = Math.max(cutout.getWaterfallInsets().top,
408                 findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP));
409         int rightInset = Math.max(cutout.getWaterfallInsets().right,
410                 findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT));
411         int bottomInset = Math.max(cutout.getWaterfallInsets().bottom,
412                 findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(),
413                         Gravity.BOTTOM));
414 
415         return new Rect(leftInset, topInset, rightInset, bottomInset);
416     }
417 
findCutoutInsetForSide(Size display, Rect boundingRect, int gravity)418     private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
419         if (boundingRect.isEmpty()) {
420             return 0;
421         }
422 
423         int inset = 0;
424         switch (gravity) {
425             case Gravity.TOP:
426                 return Math.max(inset, boundingRect.bottom);
427             case Gravity.BOTTOM:
428                 return Math.max(inset, display.getHeight() - boundingRect.top);
429             case Gravity.LEFT:
430                 return Math.max(inset, boundingRect.right);
431             case Gravity.RIGHT:
432                 return Math.max(inset, display.getWidth() - boundingRect.left);
433             default:
434                 throw new IllegalArgumentException("unknown gravity: " + gravity);
435         }
436     }
437 
hasNavigationBar(DisplayInfo info, Context context, int displayId)438     static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) {
439         if (displayId == Display.DEFAULT_DISPLAY) {
440             // Allow a system property to override this. Used by the emulator.
441             final String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
442             if ("1".equals(navBarOverride)) {
443                 return false;
444             } else if ("0".equals(navBarOverride)) {
445                 return true;
446             }
447             return context.getResources().getBoolean(R.bool.config_showNavigationBar);
448         } else {
449             boolean isUntrustedVirtualDisplay = info.type == Display.TYPE_VIRTUAL
450                     && info.ownerUid != SYSTEM_UID;
451             final ContentResolver resolver = context.getContentResolver();
452             boolean forceDesktopOnExternal = Settings.Global.getInt(resolver,
453                     DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
454 
455             return ((info.flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
456                     || (forceDesktopOnExternal && !isUntrustedVirtualDisplay));
457             // TODO(b/142569966): make sure VR2D and DisplayWindowSettings are moved here somehow.
458         }
459     }
460 
hasStatusBar(int displayId)461     static boolean hasStatusBar(int displayId) {
462         return displayId == Display.DEFAULT_DISPLAY;
463     }
464 
465     /** Retrieve navigation bar position from resources based on rotation and size. */
navigationBarPosition(Resources res, int displayWidth, int displayHeight, int rotation)466     public static @NavBarPosition int navigationBarPosition(Resources res, int displayWidth,
467             int displayHeight, int rotation) {
468         boolean navBarCanMove = displayWidth != displayHeight && res.getBoolean(
469                 com.android.internal.R.bool.config_navBarCanMove);
470         if (navBarCanMove && displayWidth > displayHeight) {
471             if (rotation == Surface.ROTATION_90) {
472                 return NAV_BAR_RIGHT;
473             } else {
474                 return NAV_BAR_LEFT;
475             }
476         }
477         return NAV_BAR_BOTTOM;
478     }
479 
480     /** Retrieve navigation bar size from resources based on side/orientation/ui-mode */
getNavigationBarSize(Resources res, int navBarSide, boolean landscape, int uiMode)481     public static int getNavigationBarSize(Resources res, int navBarSide, boolean landscape,
482             int uiMode) {
483         final boolean carMode = (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR;
484         if (carMode) {
485             if (navBarSide == NAV_BAR_BOTTOM) {
486                 return res.getDimensionPixelSize(landscape
487                         ? R.dimen.navigation_bar_height_landscape_car_mode
488                         : R.dimen.navigation_bar_height_car_mode);
489             } else {
490                 return res.getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode);
491             }
492 
493         } else {
494             if (navBarSide == NAV_BAR_BOTTOM) {
495                 return res.getDimensionPixelSize(landscape
496                         ? R.dimen.navigation_bar_height_landscape
497                         : R.dimen.navigation_bar_height);
498             } else {
499                 return res.getDimensionPixelSize(R.dimen.navigation_bar_width);
500             }
501         }
502     }
503 
504     /** @see com.android.server.wm.DisplayPolicy#getNavigationBarFrameHeight */
getNavigationBarFrameHeight(Resources res, boolean landscape)505     public static int getNavigationBarFrameHeight(Resources res, boolean landscape) {
506         return res.getDimensionPixelSize(landscape
507                 ? R.dimen.navigation_bar_frame_height_landscape
508                 : R.dimen.navigation_bar_frame_height);
509     }
510 }
511