1 /* 2 * Copyright (C) 2024 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.layoutlib.bridge.util; 18 19 import com.android.layoutlib.bridge.bars.NavigationHandle; 20 21 import android.app.ResourcesManager; 22 import android.content.Context; 23 import android.content.res.Resources; 24 import android.graphics.Insets; 25 import android.graphics.PixelFormat; 26 import android.graphics.Rect; 27 import android.util.DisplayMetrics; 28 import android.util.TypedValue; 29 import android.view.Gravity; 30 import android.view.InsetsController; 31 import android.view.InsetsFrameProvider; 32 import android.view.InsetsSource; 33 import android.view.InsetsState; 34 import android.view.Surface; 35 import android.view.View; 36 import android.view.WindowInsets; 37 import android.view.WindowManager; 38 39 import java.util.List; 40 41 import static android.app.WindowConfiguration.ROTATION_UNDEFINED; 42 import static android.view.InsetsSource.FLAG_SUPPRESS_SCRIM; 43 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 44 45 public class InsetUtil { getCurrentBounds(Context context)46 public static Rect getCurrentBounds(Context context) { 47 synchronized (ResourcesManager.getInstance()) { 48 return context.getResources().getConfiguration().windowConfiguration.getBounds(); 49 } 50 } 51 52 /** 53 * This applies all insets provided by the System UI. 54 * This is a simplified version of what happens in 55 * services/core/java/com/android/server/wm/DisplayPolicy.java. 56 */ setupSysUiInsets(Context context, InsetsController insetsController, List<InsetsFrameProvider> insetsFrameProviders)57 public static void setupSysUiInsets(Context context, InsetsController insetsController, 58 List<InsetsFrameProvider> insetsFrameProviders) { 59 Rect currentBounds = getCurrentBounds(context); 60 insetsController.onFrameChanged(currentBounds); 61 InsetsState insetsState = insetsController.getState(); 62 Rect tmpRect = new Rect(); 63 // First set the window frame to all inset sources 64 for (InsetsFrameProvider provider : insetsFrameProviders) { 65 InsetsSource source = 66 insetsState.getOrCreateSource(provider.getId(), provider.getType()); 67 source.getFrame().set(currentBounds); 68 } 69 // Then apply the insets 70 for (InsetsFrameProvider provider : insetsFrameProviders) { 71 Insets insets = provider.getInsetsSize(); 72 InsetsSource source = 73 insetsState.getOrCreateSource(provider.getId(), provider.getType()); 74 Rect sourceFrame = source.getFrame(); 75 if (provider.getMinimalInsetsSizeInDisplayCutoutSafe() != null) { 76 tmpRect.set(sourceFrame); 77 } 78 source.updateSideHint(currentBounds); 79 calculateInsetsFrame(sourceFrame, insets); 80 81 if (provider.getMinimalInsetsSizeInDisplayCutoutSafe() != null) { 82 // The insets is at least with the given size within the display cutout safe area. 83 // Calculate the smallest size. 84 calculateInsetsFrame(tmpRect, provider.getMinimalInsetsSizeInDisplayCutoutSafe()); 85 // If it's larger than previous calculation, use it. 86 if (tmpRect.contains(sourceFrame)) { 87 sourceFrame.set(tmpRect); 88 } 89 } 90 } 91 } 92 93 // Copied from services/core/java/com/android/server/wm/DisplayPolicy.java calculateInsetsFrame(Rect inOutFrame, Insets insetsSize)94 private static void calculateInsetsFrame(Rect inOutFrame, Insets insetsSize) { 95 if (insetsSize == null) { 96 inOutFrame.setEmpty(); 97 return; 98 } 99 // Only one side of the provider shall be applied. Check in the order of left - top - 100 // right - bottom, only the first non-zero value will be applied. 101 if (insetsSize.left != 0) { 102 inOutFrame.right = inOutFrame.left + insetsSize.left; 103 } else if (insetsSize.top != 0) { 104 inOutFrame.bottom = inOutFrame.top + insetsSize.top; 105 } else if (insetsSize.right != 0) { 106 inOutFrame.left = inOutFrame.right - insetsSize.right; 107 } else if (insetsSize.bottom != 0) { 108 inOutFrame.top = inOutFrame.bottom - insetsSize.bottom; 109 } else { 110 inOutFrame.setEmpty(); 111 } 112 } 113 114 // Copied/adapted from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java getNavBarLayoutParamsForRotation(Context context, View navBar, int rotation)115 public static WindowManager.LayoutParams getNavBarLayoutParamsForRotation(Context context, 116 View navBar, int rotation) { 117 int width = WindowManager.LayoutParams.MATCH_PARENT; 118 int height = WindowManager.LayoutParams.MATCH_PARENT; 119 int insetsHeight = -1; 120 int gravity = Gravity.BOTTOM; 121 boolean navBarCanMove = true; 122 WindowManager windowManager = context.getSystemService(WindowManager.class); 123 if (windowManager != null) { 124 Rect displaySize = windowManager.getCurrentWindowMetrics().getBounds(); 125 navBarCanMove = displaySize.width() != displaySize.height() && 126 context.getResources().getBoolean( 127 com.android.internal.R.bool.config_navBarCanMove); 128 } 129 if (!navBarCanMove) { 130 height = context.getResources().getDimensionPixelSize( 131 com.android.internal.R.dimen.navigation_bar_frame_height); 132 insetsHeight = context.getResources().getDimensionPixelSize( 133 com.android.internal.R.dimen.navigation_bar_height); 134 } else { 135 switch (rotation) { 136 case ROTATION_UNDEFINED: 137 case Surface.ROTATION_0: 138 case Surface.ROTATION_180: 139 height = context.getResources().getDimensionPixelSize( 140 com.android.internal.R.dimen.navigation_bar_frame_height); 141 insetsHeight = context.getResources().getDimensionPixelSize( 142 com.android.internal.R.dimen.navigation_bar_height); 143 break; 144 case Surface.ROTATION_90: 145 gravity = Gravity.RIGHT; 146 width = context.getResources().getDimensionPixelSize( 147 com.android.internal.R.dimen.navigation_bar_width); 148 break; 149 case Surface.ROTATION_270: 150 gravity = Gravity.LEFT; 151 width = context.getResources().getDimensionPixelSize( 152 com.android.internal.R.dimen.navigation_bar_width); 153 break; 154 } 155 } 156 WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height, 157 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, 158 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 159 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | 160 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 161 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | 162 WindowManager.LayoutParams.FLAG_SLIPPERY, PixelFormat.TRANSLUCENT); 163 lp.gravity = gravity; 164 lp.providedInsets = getInsetsFrameProvider(navBar, insetsHeight, context); 165 166 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC | 167 WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; 168 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 169 return lp; 170 } 171 172 // Copied/adapted from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java getInsetsFrameProvider(View navBar, int insetsHeight, Context userContext)173 private static InsetsFrameProvider[] getInsetsFrameProvider(View navBar, int insetsHeight, 174 Context userContext) { 175 final InsetsFrameProvider navBarProvider = 176 new InsetsFrameProvider(navBar, 0, WindowInsets.Type.navigationBars()); 177 if (insetsHeight != -1) { 178 navBarProvider.setInsetsSize(Insets.of(0, 0, 0, insetsHeight)); 179 } 180 final boolean needsScrim = userContext.getResources().getBoolean( 181 com.android.internal.R.bool.config_navBarNeedsScrim); 182 navBarProvider.setFlags(needsScrim ? 0 : FLAG_SUPPRESS_SCRIM, FLAG_SUPPRESS_SCRIM); 183 184 final InsetsFrameProvider tappableElementProvider = 185 new InsetsFrameProvider(navBar, 0, WindowInsets.Type.tappableElement()); 186 final boolean tapThrough = userContext.getResources().getBoolean( 187 com.android.internal.R.bool.config_navBarTapThrough); 188 if (tapThrough) { 189 tappableElementProvider.setInsetsSize(Insets.NONE); 190 } 191 192 final int gestureHeight = userContext.getResources().getDimensionPixelSize( 193 com.android.internal.R.dimen.navigation_bar_gesture_height); 194 final boolean handlingGesture = navBar instanceof NavigationHandle; 195 final InsetsFrameProvider mandatoryGestureProvider = 196 new InsetsFrameProvider(navBar, 0, WindowInsets.Type.mandatorySystemGestures()); 197 if (handlingGesture) { 198 mandatoryGestureProvider.setInsetsSize(Insets.of(0, 0, 0, gestureHeight)); 199 } 200 final int gestureInset = handlingGesture ? getUnscaledInset(userContext.getResources()) : 0; 201 return new InsetsFrameProvider[]{navBarProvider, tappableElementProvider, 202 mandatoryGestureProvider, 203 new InsetsFrameProvider(navBar, 0, WindowInsets.Type.systemGestures()).setSource( 204 InsetsFrameProvider.SOURCE_DISPLAY).setInsetsSize( 205 Insets.of(gestureInset, 0, 0, 0)).setMinimalInsetsSizeInDisplayCutoutSafe( 206 Insets.of(gestureInset, 0, 0, 0)), 207 new InsetsFrameProvider(navBar, 1, WindowInsets.Type.systemGestures()).setSource( 208 InsetsFrameProvider.SOURCE_DISPLAY).setInsetsSize( 209 Insets.of(0, 0, gestureInset, 0)).setMinimalInsetsSizeInDisplayCutoutSafe( 210 Insets.of(0, 0, gestureInset, 0))}; 211 } 212 213 // Copied/adapted from packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java getUnscaledInset(Resources userRes)214 private static int getUnscaledInset(Resources userRes) { 215 final DisplayMetrics dm = userRes.getDisplayMetrics(); 216 final float defaultInset = 217 userRes.getDimension(com.android.internal.R.dimen.config_backGestureInset) / 218 dm.density; 219 final float inset = 220 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, defaultInset, dm); 221 return (int) inset; 222 } 223 } 224