1 /* 2 * Copyright (C) 2020 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 package android.view; 17 18 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 19 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 20 import static com.android.layoutlib.bridge.util.InsetUtil.getCurrentBounds; 21 22 import android.content.Context; 23 import android.graphics.Color; 24 import android.graphics.Point; 25 import android.graphics.Rect; 26 import android.graphics.Region; 27 import android.graphics.drawable.ColorDrawable; 28 import android.graphics.drawable.Drawable; 29 import android.os.IBinder; 30 import android.util.DisplayMetrics; 31 import android.view.Display.Mode; 32 import android.widget.FrameLayout; 33 34 import com.android.ide.common.rendering.api.ILayoutLog; 35 import com.android.internal.R; 36 import com.android.internal.policy.DecorView; 37 import com.android.layoutlib.bridge.Bridge; 38 import com.android.layoutlib.bridge.android.BridgeContext; 39 import com.android.server.wm.DisplayFrames; 40 41 import java.util.ArrayList; 42 43 public class WindowManagerImpl implements WindowManager { 44 private static final PrivacyIndicatorBounds sPrivacyIndicatorBounds = 45 new PrivacyIndicatorBounds(); 46 private final Context mContext; 47 private final DisplayMetrics mMetrics; 48 private final DisplayInfo mDisplayInfo; 49 private Display mDisplay; 50 /** 51 * Root view of the base window, new windows will be added on top of this. 52 */ 53 private ViewGroup mBaseRootView; 54 /** 55 * Root view of the current window at the top of the display, 56 * null if there is only the base window present. 57 */ 58 private ViewGroup mCurrentRootView; 59 private DisplayFrames mDisplayFrames; 60 WindowManagerImpl(BridgeContext context, DisplayMetrics metrics)61 public WindowManagerImpl(BridgeContext context, DisplayMetrics metrics) { 62 mContext = context; 63 mMetrics = metrics; 64 65 mDisplayInfo = new DisplayInfo(); 66 mDisplayInfo.logicalHeight = mMetrics.heightPixels; 67 mDisplayInfo.logicalWidth = mMetrics.widthPixels; 68 mDisplayInfo.supportedModes = new Mode[] { 69 new Mode(0, mMetrics.widthPixels, mMetrics.heightPixels, 60f) 70 }; 71 mDisplayInfo.logicalDensityDpi = mMetrics.densityDpi; 72 mDisplayInfo.displayCutout = DisplayCutout.NO_CUTOUT; 73 } 74 createLocalWindowManager(Window parentWindow)75 public WindowManagerImpl createLocalWindowManager(Window parentWindow) { 76 return this; 77 } 78 createPresentationWindowManager(Context displayContext)79 public WindowManagerImpl createPresentationWindowManager(Context displayContext) { 80 Bridge.getLog().fidelityWarning(ILayoutLog.TAG_UNSUPPORTED, 81 "The preview does not fully support multiple windows.", 82 null, null, null); 83 return this; 84 } 85 86 /** 87 * Sets the window token to assign when none is specified by the client or 88 * available from the parent window. 89 * 90 * @param token The default token to assign. 91 */ setDefaultToken(IBinder token)92 public void setDefaultToken(IBinder token) { 93 94 } 95 96 @Override getDefaultDisplay()97 public Display getDefaultDisplay() { 98 if (mDisplay == null) { 99 mDisplay = new Display(null, Display.DEFAULT_DISPLAY, mDisplayInfo, 100 mContext.getResources()); 101 } 102 return mDisplay; 103 } 104 105 106 @Override addView(View arg0, android.view.ViewGroup.LayoutParams arg1)107 public void addView(View arg0, android.view.ViewGroup.LayoutParams arg1) { 108 if (mBaseRootView == null) { 109 return; 110 } 111 if (mCurrentRootView == null) { 112 FrameLayout layout = new FrameLayout(mContext) { 113 @Override 114 public boolean dispatchTouchEvent(MotionEvent ev) { 115 float offsetX = - getX(); 116 float offsetY = - getY(); 117 View baseRootParent = (View)mBaseRootView.getParent(); 118 if (baseRootParent != null) { 119 offsetX -= baseRootParent.getX(); 120 offsetY -= baseRootParent.getY(); 121 } 122 ev.offsetLocation(offsetX, offsetY); 123 return super.dispatchTouchEvent(ev); 124 } 125 126 @Override 127 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, 128 int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 129 // This reproduces ViewRootImpl#measureHierarchy as this FrameLayout should 130 // be treated as a ViewRoot. 131 ViewGroup.LayoutParams lp = child.getLayoutParams(); 132 int parentWidth = MeasureSpec.getSize(parentWidthMeasureSpec); 133 int parentHeight = MeasureSpec.getSize(parentHeightMeasureSpec); 134 int childWidthMeasureSpec = 0; 135 int childHeightMeasureSpec = ViewRootImpl.getRootMeasureSpec(parentHeight, 136 lp.height, 0); 137 if (lp.width == WRAP_CONTENT) { 138 int baseSize = 139 mContext.getResources().getDimensionPixelSize(R.dimen.config_prefDialogWidth); 140 if (baseSize != 0 && baseSize < parentWidth) { 141 childWidthMeasureSpec = ViewRootImpl.getRootMeasureSpec(baseSize, 142 lp.width, 0); 143 } 144 } 145 if (childWidthMeasureSpec == 0) { 146 childWidthMeasureSpec = ViewRootImpl.getRootMeasureSpec(parentWidth, 147 lp.width, 0); 148 } 149 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 150 } 151 }; 152 // The window root view should not handle touch events. 153 // Events need to be dispatched to the base view inside the window, 154 // with coordinates shifted accordingly. 155 layout.setOnTouchListener((v, event) -> { 156 event.offsetLocation(-arg0.getX(), -arg0.getY()); 157 return arg0.dispatchTouchEvent(event); 158 }); 159 int layoutMode = WRAP_CONTENT; 160 if (arg0 instanceof DecorView) { 161 // DecorView background should cover the entire screen 162 layoutMode = MATCH_PARENT; 163 } 164 mBaseRootView.addView(layout, new FrameLayout.LayoutParams(layoutMode, layoutMode)); 165 mCurrentRootView = layout; 166 } 167 168 FrameLayout.LayoutParams frameLayoutParams = new FrameLayout.LayoutParams(arg1); 169 if (arg1 instanceof LayoutParams params) { 170 frameLayoutParams.gravity = params.gravity; 171 if ((params.flags & LayoutParams.FLAG_DIM_BEHIND) != 0) { 172 mCurrentRootView.setBackgroundColor(Color.argb(params.dimAmount, 0, 0, 0)); 173 } else { 174 int backgroundColor = Color.WHITE; 175 Drawable background = mBaseRootView.getBackground(); 176 if (background == null) { 177 background = mBaseRootView.getRootView().getBackground(); 178 } 179 if (background instanceof ColorDrawable) { 180 backgroundColor = ((ColorDrawable) background).getColor(); 181 } 182 mCurrentRootView.setBackgroundColor(backgroundColor); 183 } 184 } 185 mCurrentRootView.addView(arg0, frameLayoutParams); 186 ViewRootImpl_Accessor.setChild(mBaseRootView.getViewRootImpl(), arg0); 187 } 188 189 @Override removeView(View arg0)190 public void removeView(View arg0) { 191 ViewRootImpl viewRootImpl = arg0.getViewRootImpl(); 192 if (mCurrentRootView != null) { 193 mCurrentRootView.removeView(arg0); 194 if (mBaseRootView != null && mCurrentRootView.getChildCount() == 0) { 195 mBaseRootView.removeView(mCurrentRootView); 196 mCurrentRootView = null; 197 } 198 } 199 if (viewRootImpl != null && viewRootImpl.getView() == arg0) { 200 View newRoot = null; 201 if (mCurrentRootView != null && mCurrentRootView.getChildCount() > 0) { 202 ArrayList<View> childrenList = mCurrentRootView.buildOrderedChildList(); 203 newRoot = childrenList.get(childrenList.size() - 1); 204 } else if (mBaseRootView != null) { 205 View root = mBaseRootView; 206 while (root.getParent() instanceof View) { 207 root = (View)root.getParent(); 208 } 209 newRoot = root; 210 } 211 ViewRootImpl_Accessor.setChild(viewRootImpl, newRoot); 212 } 213 } 214 215 @Override updateViewLayout(View view, android.view.ViewGroup.LayoutParams params)216 public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) { 217 if (view == null) { 218 throw new IllegalArgumentException("view must not be null"); 219 } 220 if (!(params instanceof LayoutParams wparams)) { 221 throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); 222 } 223 224 FrameLayout.LayoutParams lparams = new FrameLayout.LayoutParams(params); 225 lparams.gravity = wparams.gravity; 226 view.setLayoutParams(lparams); 227 if (mCurrentRootView != null) { 228 Rect bounds = new Rect(); 229 mBaseRootView.getBoundsOnScreen(bounds); 230 mCurrentRootView.setX(wparams.x - bounds.left); 231 mCurrentRootView.setY(wparams.y - bounds.top); 232 mCurrentRootView.setElevation(view.getElevation()); 233 } 234 } 235 236 237 @Override removeViewImmediate(View arg0)238 public void removeViewImmediate(View arg0) { 239 removeView(arg0); 240 } 241 242 @Override getApplicationLaunchKeyboardShortcuts(int deviceId)243 public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) { 244 return new KeyboardShortcutGroup("", new ArrayList<>()); 245 } 246 247 @Override requestAppKeyboardShortcuts( KeyboardShortcutsReceiver receiver, int deviceId)248 public void requestAppKeyboardShortcuts( 249 KeyboardShortcutsReceiver receiver, int deviceId) { 250 } 251 252 @Override getCurrentImeTouchRegion()253 public Region getCurrentImeTouchRegion() { 254 return null; 255 } 256 257 @Override setShouldShowWithInsecureKeyguard(int displayId, boolean shouldShow)258 public void setShouldShowWithInsecureKeyguard(int displayId, boolean shouldShow) { 259 // pass 260 } 261 262 @Override setShouldShowSystemDecors(int displayId, boolean shouldShow)263 public void setShouldShowSystemDecors(int displayId, boolean shouldShow) { 264 // pass 265 } 266 267 @Override setDisplayImePolicy(int displayId, int imePolicy)268 public void setDisplayImePolicy(int displayId, int imePolicy) { 269 // pass 270 } 271 272 @Override getCurrentWindowMetrics()273 public WindowMetrics getCurrentWindowMetrics() { 274 final Rect bound = getCurrentBounds(mContext); 275 276 return new WindowMetrics(bound, computeWindowInsets()); 277 } 278 279 @Override getMaximumWindowMetrics()280 public WindowMetrics getMaximumWindowMetrics() { 281 return new WindowMetrics(getMaximumBounds(), computeWindowInsets()); 282 } 283 getMaximumBounds()284 private Rect getMaximumBounds() { 285 final Point displaySize = new Point(); 286 getDefaultDisplay().getRealSize(displaySize); 287 return new Rect(0, 0, displaySize.x, displaySize.y); 288 } 289 computeWindowInsets()290 private WindowInsets computeWindowInsets() { 291 if (mBaseRootView == null) { 292 return null; 293 } 294 return mBaseRootView.getViewRootImpl().getWindowInsets(true); 295 } 296 297 // ---- Extra methods for layoutlib ---- 298 setBaseRootView(ViewGroup baseRootView)299 public void setBaseRootView(ViewGroup baseRootView) { 300 // If used within Compose Preview, use the ComposeViewAdapter as the root 301 // so that the preview attributes are handled correctly. 302 ViewGroup composableRoot = findComposableRoot(baseRootView); 303 mBaseRootView = composableRoot != null ? composableRoot : baseRootView; 304 } 305 findComposableRoot(ViewGroup baseRootView)306 private ViewGroup findComposableRoot(ViewGroup baseRootView) { 307 if (baseRootView.getClass().getName().endsWith("ComposeViewAdapter")) { 308 return baseRootView; 309 } 310 for (int i = 0; i < baseRootView.getChildCount(); i++) { 311 View child = baseRootView.getChildAt(i); 312 if (child instanceof ViewGroup) { 313 ViewGroup composableRoot = findComposableRoot((ViewGroup)child); 314 if (composableRoot != null) { 315 return composableRoot; 316 } 317 } 318 } 319 return null; 320 } 321 getCurrentRootView()322 public ViewGroup getCurrentRootView() { 323 return mCurrentRootView; 324 } 325 createOrUpdateDisplayFrames(InsetsState insetsState)326 public void createOrUpdateDisplayFrames(InsetsState insetsState) { 327 if (mDisplayFrames == null) { 328 mDisplayFrames = new DisplayFrames(insetsState, mDisplayInfo, 329 mDisplayInfo.displayCutout, RoundedCorners.NO_ROUNDED_CORNERS, 330 sPrivacyIndicatorBounds, DisplayShape.NONE); 331 } else { 332 mDisplayFrames.update(mDisplayInfo.rotation, mDisplayInfo.logicalWidth, 333 mDisplayInfo.logicalHeight, mDisplayInfo.displayCutout, 334 RoundedCorners.NO_ROUNDED_CORNERS, sPrivacyIndicatorBounds, DisplayShape.NONE); 335 } 336 } 337 setupDisplayCutout()338 public void setupDisplayCutout() { 339 DisplayCutout displayCutout = 340 DisplayCutout.fromResourcesRectApproximation(mContext.getResources(), null, 341 mMetrics.widthPixels, mMetrics.heightPixels, mMetrics.widthPixels, 342 mMetrics.heightPixels); 343 if (displayCutout != null) { 344 mDisplayInfo.displayCutout = displayCutout.getRotated(mDisplayInfo.logicalWidth, 345 mDisplayInfo.logicalHeight, mDisplayInfo.rotation, 346 getDefaultDisplay().getRotation()); 347 } 348 } 349 } 350