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