• 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.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
26 
27 import android.annotation.IntDef;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.content.ContentResolver;
31 import android.content.Context;
32 import android.content.res.Resources;
33 import android.graphics.Insets;
34 import android.graphics.PointF;
35 import android.graphics.Rect;
36 import android.graphics.RectF;
37 import android.os.SystemProperties;
38 import android.provider.Settings;
39 import android.util.DisplayMetrics;
40 import android.util.Size;
41 import android.view.Display;
42 import android.view.DisplayCutout;
43 import android.view.DisplayInfo;
44 import android.view.InsetsState;
45 import android.view.Surface;
46 import android.view.WindowInsets;
47 
48 import androidx.annotation.VisibleForTesting;
49 
50 import com.android.internal.R;
51 import com.android.internal.policy.SystemBarUtils;
52 
53 import java.lang.annotation.Retention;
54 import java.lang.annotation.RetentionPolicy;
55 import java.util.Objects;
56 
57 /**
58  * Contains information about the layout-properties of a display. This refers to internal layout
59  * like insets/cutout/rotation. In general, this can be thought of as the shell analog to
60  * DisplayPolicy.
61  */
62 public class DisplayLayout {
63     @IntDef(prefix = { "NAV_BAR_" }, value = {
64             NAV_BAR_LEFT,
65             NAV_BAR_RIGHT,
66             NAV_BAR_BOTTOM,
67     })
68     @Retention(RetentionPolicy.SOURCE)
69     public @interface NavBarPosition {}
70 
71     // Navigation bar position values
72     public static final int NAV_BAR_LEFT = 1 << 0;
73     public static final int NAV_BAR_RIGHT = 1 << 1;
74     public static final int NAV_BAR_BOTTOM = 1 << 2;
75 
76     private static final String TAG = "DisplayLayout";
77 
78     private int mUiMode;
79     private int mWidth;
80     private int mHeight;
81     private RectF mGlobalBoundsDp;
82     private DisplayCutout mCutout;
83     private int mRotation;
84     private int mDensityDpi;
85     private final Rect mNonDecorInsets = new Rect();
86     private final Rect mStableInsets = new Rect();
87     private boolean mHasNavigationBar = false;
88     private boolean mHasStatusBar = false;
89     private int mNavBarFrameHeight = 0;
90     private int mTaskbarFrameHeight = 0;
91     private boolean mAllowSeamlessRotationDespiteNavBarMoving = false;
92     private boolean mNavigationBarCanMove = false;
93     private boolean mReverseDefaultRotation = false;
94     private InsetsState mInsetsState = new InsetsState();
95 
96     /**
97      * Different from {@link #equals(Object)}, this method compares the basic geometry properties
98      * of two {@link DisplayLayout} objects including width, height, rotation, density, cutout.
99      * @return {@code true} if the given {@link DisplayLayout} is identical geometry wise.
100      */
isSameGeometry(@onNull DisplayLayout other)101     public boolean isSameGeometry(@NonNull DisplayLayout other) {
102         return mWidth == other.mWidth
103                 && mHeight == other.mHeight
104                 && mRotation == other.mRotation
105                 && mDensityDpi == other.mDensityDpi
106                 && Objects.equals(mCutout, other.mCutout);
107     }
108 
109     @Override
equals(Object o)110     public boolean equals(Object o) {
111         if (this == o) return true;
112         if (!(o instanceof DisplayLayout)) return false;
113         final DisplayLayout other = (DisplayLayout) o;
114         return mUiMode == other.mUiMode
115                 && mWidth == other.mWidth
116                 && mHeight == other.mHeight
117                 && Objects.equals(mGlobalBoundsDp, other.mGlobalBoundsDp)
118                 && Objects.equals(mCutout, other.mCutout)
119                 && mRotation == other.mRotation
120                 && mDensityDpi == other.mDensityDpi
121                 && Objects.equals(mNonDecorInsets, other.mNonDecorInsets)
122                 && Objects.equals(mStableInsets, other.mStableInsets)
123                 && mHasNavigationBar == other.mHasNavigationBar
124                 && mHasStatusBar == other.mHasStatusBar
125                 && mAllowSeamlessRotationDespiteNavBarMoving
126                         == other.mAllowSeamlessRotationDespiteNavBarMoving
127                 && mNavigationBarCanMove == other.mNavigationBarCanMove
128                 && mReverseDefaultRotation == other.mReverseDefaultRotation
129                 && mNavBarFrameHeight == other.mNavBarFrameHeight
130                 && mTaskbarFrameHeight == other.mTaskbarFrameHeight
131                 && Objects.equals(mInsetsState, other.mInsetsState);
132     }
133 
134     @Override
hashCode()135     public int hashCode() {
136         return Objects.hash(mUiMode, mWidth, mHeight, mGlobalBoundsDp, mCutout, mRotation,
137                 mDensityDpi, mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
138                 mNavBarFrameHeight, mTaskbarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
139                 mNavigationBarCanMove, mReverseDefaultRotation, mInsetsState);
140     }
141 
142     /**
143      * Create empty layout.
144      */
DisplayLayout()145     public DisplayLayout() {
146     }
147 
148     /**
149      * Construct a custom display layout using a DisplayInfo.
150      * @param info
151      * @param res
152      */
DisplayLayout(DisplayInfo info, Resources res, boolean hasNavigationBar, boolean hasStatusBar)153     public DisplayLayout(DisplayInfo info, Resources res, boolean hasNavigationBar,
154             boolean hasStatusBar) {
155         init(info, res, hasNavigationBar, hasStatusBar);
156     }
157 
158     /**
159      * Construct a display layout based on a live display.
160      * @param context Used for resources.
161      */
DisplayLayout(@onNull Context context, @NonNull Display rawDisplay)162     public DisplayLayout(@NonNull Context context, @NonNull Display rawDisplay) {
163         final int displayId = rawDisplay.getDisplayId();
164         DisplayInfo info = new DisplayInfo();
165         rawDisplay.getDisplayInfo(info);
166         init(info, context.getResources(), hasNavigationBar(info, context, displayId),
167                 hasStatusBar(displayId));
168     }
169 
DisplayLayout(DisplayLayout dl)170     public DisplayLayout(DisplayLayout dl) {
171         set(dl);
172     }
173 
174     /** sets this DisplayLayout to a copy of another on. */
set(DisplayLayout dl)175     public void set(DisplayLayout dl) {
176         mUiMode = dl.mUiMode;
177         mWidth = dl.mWidth;
178         mHeight = dl.mHeight;
179         mGlobalBoundsDp = dl.mGlobalBoundsDp;
180         mCutout = dl.mCutout;
181         mRotation = dl.mRotation;
182         mDensityDpi = dl.mDensityDpi;
183         mHasNavigationBar = dl.mHasNavigationBar;
184         mHasStatusBar = dl.mHasStatusBar;
185         mAllowSeamlessRotationDespiteNavBarMoving = dl.mAllowSeamlessRotationDespiteNavBarMoving;
186         mNavigationBarCanMove = dl.mNavigationBarCanMove;
187         mReverseDefaultRotation = dl.mReverseDefaultRotation;
188         mNavBarFrameHeight = dl.mNavBarFrameHeight;
189         mTaskbarFrameHeight = dl.mTaskbarFrameHeight;
190         mNonDecorInsets.set(dl.mNonDecorInsets);
191         mStableInsets.set(dl.mStableInsets);
192         mInsetsState.set(dl.mInsetsState, true /* copySources */);
193     }
194 
init(DisplayInfo info, Resources res, boolean hasNavigationBar, boolean hasStatusBar)195     private void init(DisplayInfo info, Resources res, boolean hasNavigationBar,
196             boolean hasStatusBar) {
197         mUiMode = res.getConfiguration().uiMode;
198         mWidth = info.logicalWidth;
199         mHeight = info.logicalHeight;
200         mRotation = info.rotation;
201         mCutout = info.displayCutout;
202         mDensityDpi = info.logicalDensityDpi;
203         mGlobalBoundsDp = new RectF(0, 0, pxToDp(mWidth), pxToDp(mHeight));
204         mHasNavigationBar = hasNavigationBar;
205         mHasStatusBar = hasStatusBar;
206         mAllowSeamlessRotationDespiteNavBarMoving = res.getBoolean(
207             R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
208         mNavigationBarCanMove = res.getBoolean(R.bool.config_navBarCanMove);
209         mReverseDefaultRotation = res.getBoolean(R.bool.config_reverseDefaultRotation);
210         recalcInsets(res);
211     }
212 
213     /**
214      * Updates the current insets.
215      */
setInsets(Resources res, InsetsState state)216     public void setInsets(Resources res, InsetsState state) {
217         mInsetsState = state;
218         recalcInsets(res);
219     }
220 
221     @VisibleForTesting
recalcInsets(Resources res)222     void recalcInsets(Resources res) {
223         computeNonDecorInsets(res, mRotation, mWidth, mHeight, mCutout, mInsetsState, mUiMode,
224                 mNonDecorInsets, mHasNavigationBar);
225         mStableInsets.set(mNonDecorInsets);
226         if (mHasStatusBar) {
227             convertNonDecorInsetsToStableInsets(res, mStableInsets, mCutout, mHasStatusBar);
228         }
229         mNavBarFrameHeight = getNavigationBarFrameHeight(res, /* landscape */ mWidth > mHeight);
230         mTaskbarFrameHeight = SystemBarUtils.getTaskbarHeight(res);
231     }
232 
233     /**
234      * Apply a rotation to this layout and its parameters.
235      */
rotateTo(Resources res, @Surface.Rotation int toRotation)236     public void rotateTo(Resources res, @Surface.Rotation int toRotation) {
237         final int origWidth = mWidth;
238         final int origHeight = mHeight;
239         final int fromRotation = mRotation;
240         final int rotationDelta = (toRotation - fromRotation + 4) % 4;
241         final boolean changeOrient = (rotationDelta % 2) != 0;
242 
243         mRotation = toRotation;
244         if (changeOrient) {
245             mWidth = origHeight;
246             mHeight = origWidth;
247         }
248 
249         if (mCutout != null) {
250             mCutout = mCutout.getRotated(origWidth, origHeight, fromRotation, toRotation);
251         }
252 
253         recalcInsets(res);
254     }
255 
256     /**
257      * Update the dimensions of this layout.
258      */
resizeTo(Resources res, Size displaySize)259     public void resizeTo(Resources res, Size displaySize) {
260         mWidth = displaySize.getWidth();
261         mHeight = displaySize.getHeight();
262 
263         recalcInsets(res);
264     }
265 
266     /** Update the global bounds of this layout, in DP. */
setGlobalBoundsDp(RectF bounds)267     public void setGlobalBoundsDp(RectF bounds) {
268         mGlobalBoundsDp = bounds;
269     }
270 
271     /** Get this layout's non-decor insets. */
nonDecorInsets()272     public Rect nonDecorInsets() {
273         return mNonDecorInsets;
274     }
275 
276     /** Get this layout's stable insets. */
stableInsets()277     public Rect stableInsets() {
278         return mStableInsets;
279     }
280 
281     /** Get this layout's width in pixels. */
width()282     public int width() {
283         return mWidth;
284     }
285 
286     /** Get this layout's height in pixels. */
height()287     public int height() {
288         return mHeight;
289     }
290 
291     /** Get this layout's global bounds in the multi-display coordinate system in DP. */
globalBoundsDp()292     public RectF globalBoundsDp() {
293         return mGlobalBoundsDp;
294     }
295 
296     /** Get this layout's display rotation. */
rotation()297     public int rotation() {
298         return mRotation;
299     }
300 
301     /** Get this layout's display density. */
densityDpi()302     public int densityDpi() {
303         return mDensityDpi;
304     }
305 
306     /** Get the density scale for the display. */
density()307     public float density() {
308         return mDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
309     }
310 
311     /** Get whether this layout is landscape. */
isLandscape()312     public boolean isLandscape() {
313         return mWidth > mHeight;
314     }
315 
316     /** Get the navbar frame (or window) height (used by ime). */
navBarFrameHeight()317     public int navBarFrameHeight() {
318         return mNavBarFrameHeight;
319     }
320 
321     /** @return whether we can seamlessly rotate even if nav-bar can change sides. */
allowSeamlessRotationDespiteNavBarMoving()322     public boolean allowSeamlessRotationDespiteNavBarMoving() {
323         return mAllowSeamlessRotationDespiteNavBarMoving;
324     }
325 
326     /**
327      * Returns {@code true} if the navigation bar will change sides during rotation and the display
328      * is not square.
329      */
navigationBarCanMove()330     public boolean navigationBarCanMove() {
331         return mNavigationBarCanMove && mWidth != mHeight;
332     }
333 
334     /** @return the rotation that would make the physical display "upside down". */
getUpsideDownRotation()335     public int getUpsideDownRotation() {
336         boolean displayHardwareIsLandscape = mWidth > mHeight;
337         if ((mRotation % 2) != 0) {
338             displayHardwareIsLandscape = !displayHardwareIsLandscape;
339         }
340         if (displayHardwareIsLandscape) {
341             return mReverseDefaultRotation ? Surface.ROTATION_270 : Surface.ROTATION_90;
342         }
343         return Surface.ROTATION_180;
344     }
345 
346     /** Gets the orientation of this layout */
getOrientation()347     public int getOrientation() {
348         return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
349     }
350 
351     /** Gets the calculated stable-bounds for this layout */
getStableBounds(Rect outBounds)352     public void getStableBounds(Rect outBounds) {
353         outBounds.set(0, 0, mWidth, mHeight);
354         outBounds.inset(mStableInsets);
355     }
356 
357     /** Predicts the calculated stable bounds when in Desktop Mode. */
getStableBoundsForDesktopMode(Rect outBounds)358     public void getStableBoundsForDesktopMode(Rect outBounds) {
359         getStableBounds(outBounds);
360 
361         if (mNavBarFrameHeight != mTaskbarFrameHeight) {
362             // Currently not in pinned taskbar mode, exclude taskbar insets instead of current
363             // navigation insets from bounds.
364             outBounds.bottom = mHeight - mTaskbarFrameHeight;
365         }
366     }
367 
368     /**
369      * Gets navigation bar position for this layout
370      * @return Navigation bar position for this layout.
371      */
getNavigationBarPosition(Resources res)372     public @NavBarPosition int getNavigationBarPosition(Resources res) {
373         return navigationBarPosition(res, mWidth, mHeight, mRotation);
374     }
375 
376     /** @return {@link DisplayCutout} instance. */
377     @Nullable
getDisplayCutout()378     public DisplayCutout getDisplayCutout() {
379         return mCutout;
380     }
381 
382     /**
383      * Calculates the stable insets if we already have the non-decor insets.
384      */
convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets, DisplayCutout cutout, boolean hasStatusBar)385     private void convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets,
386             DisplayCutout cutout, boolean hasStatusBar) {
387         if (!hasStatusBar) {
388             return;
389         }
390         int statusBarHeight = SystemBarUtils.getStatusBarHeight(res, cutout);
391         inOutInsets.top = Math.max(inOutInsets.top, statusBarHeight);
392     }
393 
394     /**
395      * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
396      * bar or button bar.
397      *
398      * @param displayRotation the current display rotation
399      * @param displayWidth the current display width
400      * @param displayHeight the current display height
401      * @param displayCutout the current display cutout
402      * @param outInsets the insets to return
403      */
computeNonDecorInsets(Resources res, int displayRotation, int displayWidth, int displayHeight, DisplayCutout displayCutout, InsetsState insetsState, int uiMode, Rect outInsets, boolean hasNavigationBar)404     static void computeNonDecorInsets(Resources res, int displayRotation, int displayWidth,
405             int displayHeight, DisplayCutout displayCutout, InsetsState insetsState, int uiMode,
406             Rect outInsets, boolean hasNavigationBar) {
407         outInsets.setEmpty();
408 
409         // Only navigation bar
410         if (hasNavigationBar) {
411             final Insets insets = insetsState.calculateInsets(
412                     insetsState.getDisplayFrame(),
413                     WindowInsets.Type.navigationBars(),
414                     false /* ignoreVisibility */);
415             int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
416             int navBarSize =
417                     getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
418             if (position == NAV_BAR_BOTTOM) {
419                 outInsets.bottom = Math.max(insets.bottom , navBarSize);
420             } else if (position == NAV_BAR_RIGHT) {
421                 outInsets.right = Math.max(insets.right , navBarSize);
422             } else if (position == NAV_BAR_LEFT) {
423                 outInsets.left = Math.max(insets.left , navBarSize);
424             }
425         }
426 
427         if (displayCutout != null) {
428             outInsets.left += displayCutout.getSafeInsetLeft();
429             outInsets.top += displayCutout.getSafeInsetTop();
430             outInsets.right += displayCutout.getSafeInsetRight();
431             outInsets.bottom += displayCutout.getSafeInsetBottom();
432         }
433     }
434 
hasNavigationBar(DisplayInfo info, Context context, int displayId)435     static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) {
436         if (displayId == Display.DEFAULT_DISPLAY) {
437             // Allow a system property to override this. Used by the emulator.
438             final String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
439             if ("1".equals(navBarOverride)) {
440                 return false;
441             } else if ("0".equals(navBarOverride)) {
442                 return true;
443             }
444             return context.getResources().getBoolean(R.bool.config_showNavigationBar);
445         } else {
446             boolean isUntrustedVirtualDisplay = info.type == Display.TYPE_VIRTUAL
447                     && info.ownerUid != SYSTEM_UID;
448             final ContentResolver resolver = context.getContentResolver();
449             boolean forceDesktopOnExternal = Settings.Global.getInt(resolver,
450                     DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
451 
452             return ((info.flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
453                     || (forceDesktopOnExternal && !isUntrustedVirtualDisplay));
454             // TODO(b/142569966): make sure VR2D and DisplayWindowSettings are moved here somehow.
455         }
456     }
457 
hasStatusBar(int displayId)458     static boolean hasStatusBar(int displayId) {
459         return displayId == Display.DEFAULT_DISPLAY;
460     }
461 
462     /** Retrieve navigation bar position from resources based on rotation and size. */
navigationBarPosition(Resources res, int displayWidth, int displayHeight, int rotation)463     public static @NavBarPosition int navigationBarPosition(Resources res, int displayWidth,
464             int displayHeight, int rotation) {
465         boolean navBarCanMove = displayWidth != displayHeight && res.getBoolean(
466                 com.android.internal.R.bool.config_navBarCanMove);
467         if (navBarCanMove && displayWidth > displayHeight) {
468             if (rotation == Surface.ROTATION_90) {
469                 return NAV_BAR_RIGHT;
470             } else {
471                 return NAV_BAR_LEFT;
472             }
473         }
474         return NAV_BAR_BOTTOM;
475     }
476 
477     /** Retrieve navigation bar size from resources based on side/orientation/ui-mode */
getNavigationBarSize(Resources res, int navBarSide, boolean landscape, int uiMode)478     public static int getNavigationBarSize(Resources res, int navBarSide, boolean landscape,
479             int uiMode) {
480         final boolean carMode = (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR;
481         if (carMode) {
482             if (navBarSide == NAV_BAR_BOTTOM) {
483                 return res.getDimensionPixelSize(landscape
484                         ? R.dimen.navigation_bar_height_landscape_car_mode
485                         : R.dimen.navigation_bar_height_car_mode);
486             } else {
487                 return res.getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode);
488             }
489 
490         } else {
491             if (navBarSide == NAV_BAR_BOTTOM) {
492                 return res.getDimensionPixelSize(landscape
493                         ? R.dimen.navigation_bar_height_landscape
494                         : R.dimen.navigation_bar_height);
495             } else {
496                 return res.getDimensionPixelSize(R.dimen.navigation_bar_width);
497             }
498         }
499     }
500 
501     /** @see com.android.server.wm.DisplayPolicy#getNavigationBarFrameHeight */
getNavigationBarFrameHeight(Resources res, boolean landscape)502     public static int getNavigationBarFrameHeight(Resources res, boolean landscape) {
503         return res.getDimensionPixelSize(landscape
504                 ? R.dimen.navigation_bar_frame_height_landscape
505                 : R.dimen.navigation_bar_frame_height);
506     }
507 
508     /**
509      * Converts a pixel value to a density-independent pixel (dp) value.
510      *
511      * @param px The pixel value to convert.
512      * @return The equivalent value in DP units.
513      */
pxToDp(Number px)514     public float pxToDp(Number px) {
515         return px.floatValue() * DisplayMetrics.DENSITY_DEFAULT / mDensityDpi;
516     }
517 
518     /**
519      * Converts a density-independent pixel (dp) value to a pixel value.
520      *
521      * @param dp The DP value to convert.
522      * @return The equivalent value in pixel units.
523      */
dpToPx(Number dp)524     public float dpToPx(Number dp) {
525         return dp.floatValue() * mDensityDpi / DisplayMetrics.DENSITY_DEFAULT;
526     }
527 
528     /**
529      * Converts local pixel coordinates on this layout to global DP coordinates.
530      *
531      * @param xPx The x-coordinate in pixels, relative to the layout's origin.
532      * @param yPx The y-coordinate in pixels, relative to the layout's origin.
533      * @return A PointF object representing the coordinates in global DP units.
534      */
localPxToGlobalDp(Number xPx, Number yPx)535     public PointF localPxToGlobalDp(Number xPx, Number yPx) {
536         return new PointF(mGlobalBoundsDp.left + pxToDp(xPx),
537                 mGlobalBoundsDp.top + pxToDp(yPx));
538     }
539 
540     /**
541      * Converts global DP coordinates to local pixel coordinates on this layout.
542      *
543      * @param xDp The x-coordinate in global DP units.
544      * @param yDp The y-coordinate in global DP units.
545      * @return A PointF object representing the coordinates in local pixel units on this layout.
546      */
globalDpToLocalPx(Number xDp, Number yDp)547     public PointF globalDpToLocalPx(Number xDp, Number yDp) {
548         return new PointF(dpToPx(xDp.floatValue() - mGlobalBoundsDp.left),
549                 dpToPx(yDp.floatValue() - mGlobalBoundsDp.top));
550     }
551 }
552