1 /*
2  * Copyright (C) 2022 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.window;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
20 import static android.view.Surface.ROTATION_0;
21 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
22 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
23 
24 import android.annotation.NonNull;
25 import android.app.ResourcesManager;
26 import android.app.WindowConfiguration;
27 import android.content.Context;
28 import android.content.res.CompatibilityInfo;
29 import android.content.res.Configuration;
30 import android.graphics.Rect;
31 import android.os.IBinder;
32 import android.os.RemoteException;
33 import android.util.DisplayMetrics;
34 import android.view.Display;
35 import android.view.DisplayCutout;
36 import android.view.DisplayInfo;
37 import android.view.InsetsState;
38 import android.view.WindowInsets;
39 import android.view.WindowManager;
40 import android.view.WindowManagerGlobal;
41 import android.view.WindowMetrics;
42 
43 import java.util.HashSet;
44 import java.util.List;
45 import java.util.Set;
46 import java.util.function.Supplier;
47 
48 /**
49  * A controller to handle {@link android.view.WindowMetrics} related APIs, which are
50  * <ol>
51  *     <li>{@link WindowManager#getCurrentWindowMetrics()}</li>
52  *     <li>{@link WindowManager#getMaximumWindowMetrics()}</li>
53  *     <li>{@link WindowManager#getPossibleMaximumWindowMetrics(int)}</li>
54  * </ol>
55  *
56  * @hide
57  */
58 public final class WindowMetricsController {
59     private final Context mContext;
60 
WindowMetricsController(@onNull Context context)61     public WindowMetricsController(@NonNull Context context) {
62         mContext = context;
63     }
64 
65     /** @see WindowManager#getCurrentWindowMetrics() */
getCurrentWindowMetrics()66     public WindowMetrics getCurrentWindowMetrics() {
67         return getWindowMetricsInternal(false /* isMaximum */);
68     }
69 
70     /** @see WindowManager#getMaximumWindowMetrics() */
getMaximumWindowMetrics()71     public WindowMetrics getMaximumWindowMetrics() {
72         return getWindowMetricsInternal(true /* isMaximum */);
73     }
74 
75     /**
76      * The core implementation to obtain {@link WindowMetrics}
77      *
78      * @param isMaximum {@code true} to obtain {@link WindowManager#getCurrentWindowMetrics()}.
79      *                  {@code false} to obtain {@link WindowManager#getMaximumWindowMetrics()}.
80      */
getWindowMetricsInternal(boolean isMaximum)81     private WindowMetrics getWindowMetricsInternal(boolean isMaximum) {
82         final Rect bounds;
83         final float density;
84         final boolean isScreenRound;
85         final int activityType;
86         synchronized (ResourcesManager.getInstance()) {
87             final Configuration config = mContext.getResources().getConfiguration();
88             final WindowConfiguration winConfig = config.windowConfiguration;
89             bounds = (isMaximum) ? winConfig.getMaxBounds() : winConfig.getBounds();
90             // Multiply default density scale because WindowMetrics provide the density value with
91             // the scaling factor for the Density Independent Pixel unit, which is the same unit
92             // as DisplayMetrics#density
93             density = config.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
94             isScreenRound = config.isScreenRound();
95             activityType = winConfig.getActivityType();
96         }
97         final IBinder token = Context.getToken(mContext);
98         final Supplier<WindowInsets> insetsSupplier = () -> getWindowInsetsFromServerForDisplay(
99                 mContext.getDisplayId(), token, bounds, isScreenRound, activityType);
100         return new WindowMetrics(new Rect(bounds), insetsSupplier, density);
101     }
102 
103     /**
104      * Retrieves WindowInsets for the given context and display, given the window bounds.
105      *
106      * @param displayId the ID of the logical display to calculate insets for
107      * @param token the token of Activity or WindowContext
108      * @param bounds the window bounds to calculate insets for
109      * @param isScreenRound if the display identified by displayId is round
110      * @param activityType the activity type of the window to calculate insets for
111      * @return WindowInsets calculated for the given window bounds, on the given display
112      */
getWindowInsetsFromServerForDisplay(int displayId, IBinder token, Rect bounds, boolean isScreenRound, int activityType)113     private static WindowInsets getWindowInsetsFromServerForDisplay(int displayId, IBinder token,
114             Rect bounds, boolean isScreenRound, int activityType) {
115         try {
116             final InsetsState insetsState = new InsetsState();
117             WindowManagerGlobal.getWindowManagerService().getWindowInsets(
118                     displayId, token, insetsState);
119             final float overrideInvScale = CompatibilityInfo.getOverrideInvertedScale();
120             if (overrideInvScale != 1f) {
121                 insetsState.scale(overrideInvScale);
122             }
123             return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState */,
124                     isScreenRound, SOFT_INPUT_ADJUST_NOTHING, 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
125                     WindowManager.LayoutParams.INVALID_WINDOW_TYPE, activityType,
126                     null /* idSideMap */);
127         } catch (RemoteException e) {
128             throw e.rethrowFromSystemServer();
129         }
130     }
131 
132     /** @see WindowManager#getPossibleMaximumWindowMetrics(int) */
133     @NonNull
getPossibleMaximumWindowMetrics(int displayId)134     public Set<WindowMetrics> getPossibleMaximumWindowMetrics(int displayId) {
135         List<DisplayInfo> possibleDisplayInfos;
136         try {
137             possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService()
138                     .getPossibleDisplayInfo(displayId);
139         } catch (RemoteException e) {
140             throw e.rethrowFromSystemServer();
141         }
142 
143         Set<WindowMetrics> maxMetrics = new HashSet<>();
144         WindowInsets windowInsets;
145         DisplayInfo currentDisplayInfo;
146         for (int i = 0; i < possibleDisplayInfos.size(); i++) {
147             currentDisplayInfo = possibleDisplayInfos.get(i);
148 
149             // Calculate max bounds for natural rotation and state.
150             Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
151                     currentDisplayInfo.getNaturalHeight());
152 
153             // Calculate insets for the natural max bounds.
154             final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
155             // Initialize insets based on Surface.ROTATION_0. Note any window-provided insets
156             // will not be set.
157             windowInsets = getWindowInsetsFromServerForDisplay(
158                     currentDisplayInfo.displayId, null /* token */,
159                     new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
160                             currentDisplayInfo.getNaturalHeight()), isScreenRound,
161                     ACTIVITY_TYPE_UNDEFINED);
162             // Set the hardware-provided insets. Always with the ROTATION_0 result.
163             DisplayCutout cutout = currentDisplayInfo.displayCutout;
164             if (cutout != null && currentDisplayInfo.rotation != ROTATION_0) {
165                 cutout = cutout.getRotated(
166                         currentDisplayInfo.logicalWidth, currentDisplayInfo.logicalHeight,
167                         currentDisplayInfo.rotation, ROTATION_0);
168             }
169             windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
170                     currentDisplayInfo.roundedCorners).setDisplayCutout(cutout).build();
171 
172             // Multiply default density scale because WindowMetrics provide the density value with
173             // the scaling factor for the Density Independent Pixel unit, which is the same unit
174             // as DisplayMetrics#density
175             final float density = currentDisplayInfo.logicalDensityDpi
176                     * DisplayMetrics.DENSITY_DEFAULT_SCALE;
177             maxMetrics.add(new WindowMetrics(maxBounds, windowInsets, density));
178         }
179         return maxMetrics;
180     }
181 }
182