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