blob: 194679cc626eb24c22760915e13aca6edea08387 [file] [log] [blame]
Adrian Roos5b518852018-01-23 17:23:38 +01001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.systemui;
16
Adrian Roos51072a82018-04-10 15:17:08 -070017import static android.view.Surface.ROTATION_0;
18import static android.view.Surface.ROTATION_180;
19import static android.view.Surface.ROTATION_270;
20import static android.view.Surface.ROTATION_90;
Adrian Roos5b518852018-01-23 17:23:38 +010021import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
22import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
23import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
24
Adrian Roos5b518852018-01-23 17:23:38 +010025import static com.android.systemui.tuner.TunablePadding.FLAG_END;
Beverlye91f0d02018-05-15 14:40:47 -040026import static com.android.systemui.tuner.TunablePadding.FLAG_START;
Adrian Roos5b518852018-01-23 17:23:38 +010027
Adrian Roos51072a82018-04-10 15:17:08 -070028import android.annotation.Dimension;
Adrian Roos5b518852018-01-23 17:23:38 +010029import android.app.Fragment;
30import android.content.Context;
31import android.content.res.ColorStateList;
32import android.content.res.Configuration;
33import android.graphics.Canvas;
34import android.graphics.Color;
Adrian Roos51072a82018-04-10 15:17:08 -070035import android.graphics.Matrix;
Adrian Roos5b518852018-01-23 17:23:38 +010036import android.graphics.Paint;
37import android.graphics.Path;
38import android.graphics.PixelFormat;
39import android.graphics.Rect;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010040import android.graphics.Region;
Adrian Roos5b518852018-01-23 17:23:38 +010041import android.hardware.display.DisplayManager;
Adrian Roos56d1a2c2018-03-08 23:22:19 +010042import android.os.SystemProperties;
Adrian Roos5b518852018-01-23 17:23:38 +010043import android.provider.Settings.Secure;
44import android.support.annotation.VisibleForTesting;
45import android.util.DisplayMetrics;
46import android.view.DisplayCutout;
47import android.view.DisplayInfo;
48import android.view.Gravity;
49import android.view.LayoutInflater;
Adrian Roos51072a82018-04-10 15:17:08 -070050import android.view.Surface;
Adrian Roos5b518852018-01-23 17:23:38 +010051import android.view.View;
52import android.view.View.OnLayoutChangeListener;
53import android.view.ViewGroup;
54import android.view.ViewGroup.LayoutParams;
Adrian Roos28c25e22018-05-31 18:07:28 +020055import android.view.ViewTreeObserver;
Adrian Roos5b518852018-01-23 17:23:38 +010056import android.view.WindowManager;
57import android.widget.FrameLayout;
58import android.widget.ImageView;
59
Evan Lairdb0506ca2018-03-15 10:34:31 -040060import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
Adrian Roos5b518852018-01-23 17:23:38 +010061import com.android.systemui.fragments.FragmentHostManager;
62import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
63import com.android.systemui.plugins.qs.QS;
64import com.android.systemui.qs.SecureSetting;
65import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
66import com.android.systemui.statusbar.phone.StatusBar;
67import com.android.systemui.tuner.TunablePadding;
68import com.android.systemui.tuner.TunerService;
69import com.android.systemui.tuner.TunerService.Tunable;
Beverlye91f0d02018-05-15 14:40:47 -040070import com.android.systemui.util.leak.RotationUtils;
Adrian Roos5b518852018-01-23 17:23:38 +010071
72/**
73 * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
74 * for antialiasing and emulation purposes.
75 */
76public class ScreenDecorations extends SystemUI implements Tunable {
77 public static final String SIZE = "sysui_rounded_size";
78 public static final String PADDING = "sysui_rounded_content_padding";
Adrian Roos56d1a2c2018-03-08 23:22:19 +010079 private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
80 SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
Adrian Roos5b518852018-01-23 17:23:38 +010081
Beverlye91f0d02018-05-15 14:40:47 -040082 private DisplayManager mDisplayManager;
83 private DisplayManager.DisplayListener mDisplayListener;
84
Adrian Roos5b518852018-01-23 17:23:38 +010085 private int mRoundedDefault;
Beverlya5f7a302018-04-25 09:19:05 -040086 private int mRoundedDefaultTop;
87 private int mRoundedDefaultBottom;
Adrian Roos5b518852018-01-23 17:23:38 +010088 private View mOverlay;
89 private View mBottomOverlay;
90 private float mDensity;
91 private WindowManager mWindowManager;
Beverlye91f0d02018-05-15 14:40:47 -040092 private int mRotation;
Adrian Roosbeeb7f42018-05-31 19:54:53 +020093 private DisplayCutoutView mCutoutTop;
94 private DisplayCutoutView mCutoutBottom;
Adrian Roos28c25e22018-05-31 18:07:28 +020095 private boolean mPendingRotationChange;
Adrian Roos5b518852018-01-23 17:23:38 +010096
97 @Override
98 public void start() {
99 mWindowManager = mContext.getSystemService(WindowManager.class);
100 mRoundedDefault = mContext.getResources().getDimensionPixelSize(
101 R.dimen.rounded_corner_radius);
Beverlya5f7a302018-04-25 09:19:05 -0400102 mRoundedDefaultTop = mContext.getResources().getDimensionPixelSize(
103 R.dimen.rounded_corner_radius_top);
104 mRoundedDefaultBottom = mContext.getResources().getDimensionPixelSize(
105 R.dimen.rounded_corner_radius_bottom);
106 if (hasRoundedCorners() || shouldDrawCutout()) {
Adrian Roos5b518852018-01-23 17:23:38 +0100107 setupDecorations();
108 }
Beverlya5f7a302018-04-25 09:19:05 -0400109
Adrian Roos5b518852018-01-23 17:23:38 +0100110 int padding = mContext.getResources().getDimensionPixelSize(
111 R.dimen.rounded_corner_content_padding);
112 if (padding != 0) {
113 setupPadding(padding);
114 }
Beverlye91f0d02018-05-15 14:40:47 -0400115
116 mDisplayListener = new DisplayManager.DisplayListener() {
117 @Override
118 public void onDisplayAdded(int displayId) {
119 // do nothing
120 }
121
122 @Override
123 public void onDisplayRemoved(int displayId) {
124 // do nothing
125 }
126
127 @Override
128 public void onDisplayChanged(int displayId) {
Jorim Jaggi0fca17c2018-06-18 14:24:11 +0200129 if (mOverlay != null && mBottomOverlay != null
130 && mRotation != RotationUtils.getExactRotation(mContext)) {
Adrian Roos28c25e22018-05-31 18:07:28 +0200131 // We cannot immediately update the orientation. Otherwise
132 // WindowManager is still deferring layout until it has finished dispatching
133 // the config changes, which may cause divergence between what we draw
134 // (new orientation), and where we are placed on the screen (old orientation).
135 // Instead we wait until either:
136 // - we are trying to redraw. This because WM resized our window and told us to.
137 // - the config change has been dispatched, so WM is no longer deferring layout.
138 mPendingRotationChange = true;
139 mOverlay.getViewTreeObserver().addOnPreDrawListener(
140 new RestartingPreDrawListener(mOverlay));
141 mBottomOverlay.getViewTreeObserver().addOnPreDrawListener(
142 new RestartingPreDrawListener(mBottomOverlay));
143
144 }
Beverlye91f0d02018-05-15 14:40:47 -0400145 updateOrientation();
146 }
147 };
148
149 mRotation = -1;
150 mDisplayManager = (DisplayManager) mContext.getSystemService(
151 Context.DISPLAY_SERVICE);
152 mDisplayManager.registerDisplayListener(mDisplayListener, null);
Adrian Roos5b518852018-01-23 17:23:38 +0100153 }
154
155 private void setupDecorations() {
156 mOverlay = LayoutInflater.from(mContext)
157 .inflate(R.layout.rounded_corners, null);
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200158 mCutoutTop = new DisplayCutoutView(mContext, true,
Adrian Roos28c25e22018-05-31 18:07:28 +0200159 this::updateWindowVisibilities, this);
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200160 ((ViewGroup)mOverlay).addView(mCutoutTop);
Adrian Roos5b518852018-01-23 17:23:38 +0100161 mBottomOverlay = LayoutInflater.from(mContext)
162 .inflate(R.layout.rounded_corners, null);
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200163 mCutoutBottom = new DisplayCutoutView(mContext, false,
Adrian Roos28c25e22018-05-31 18:07:28 +0200164 this::updateWindowVisibilities, this);
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200165 ((ViewGroup)mBottomOverlay).addView(mCutoutBottom);
Adrian Roos5b518852018-01-23 17:23:38 +0100166
167 mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
168 mOverlay.setAlpha(0);
169
170 mBottomOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
171 mBottomOverlay.setAlpha(0);
172
173 updateViews();
174
175 mWindowManager.addView(mOverlay, getWindowLayoutParams());
176 mWindowManager.addView(mBottomOverlay, getBottomLayoutParams());
177
178 DisplayMetrics metrics = new DisplayMetrics();
179 mWindowManager.getDefaultDisplay().getMetrics(metrics);
180 mDensity = metrics.density;
181
182 Dependency.get(TunerService.class).addTunable(this, SIZE);
183
184 // Watch color inversion and invert the overlay as needed.
185 SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER),
186 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
187 @Override
188 protected void handleValueChanged(int value, boolean observedChange) {
189 int tint = value != 0 ? Color.WHITE : Color.BLACK;
190 ColorStateList tintList = ColorStateList.valueOf(tint);
191 ((ImageView) mOverlay.findViewById(R.id.left)).setImageTintList(tintList);
192 ((ImageView) mOverlay.findViewById(R.id.right)).setImageTintList(tintList);
193 ((ImageView) mBottomOverlay.findViewById(R.id.left)).setImageTintList(tintList);
194 ((ImageView) mBottomOverlay.findViewById(R.id.right)).setImageTintList(tintList);
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200195 mCutoutTop.setColor(tint);
196 mCutoutBottom.setColor(tint);
Adrian Roos5b518852018-01-23 17:23:38 +0100197 }
198 };
199 setting.setListening(true);
200 setting.onChange(false);
201
202 mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
203 @Override
204 public void onLayoutChange(View v, int left, int top, int right, int bottom,
205 int oldLeft,
206 int oldTop, int oldRight, int oldBottom) {
207 mOverlay.removeOnLayoutChangeListener(this);
208 mOverlay.animate()
209 .alpha(1)
210 .setDuration(1000)
211 .start();
212 mBottomOverlay.animate()
213 .alpha(1)
214 .setDuration(1000)
215 .start();
216 }
217 });
218 }
219
220 @Override
221 protected void onConfigurationChanged(Configuration newConfig) {
Adrian Roos28c25e22018-05-31 18:07:28 +0200222 mPendingRotationChange = false;
Beverlye91f0d02018-05-15 14:40:47 -0400223 updateOrientation();
Beverly5c2b48a2018-05-24 09:25:45 -0400224 if (shouldDrawCutout() && mOverlay == null) {
225 setupDecorations();
226 }
Beverlye91f0d02018-05-15 14:40:47 -0400227 }
228
229 protected void updateOrientation() {
Adrian Roos28c25e22018-05-31 18:07:28 +0200230 if (mPendingRotationChange) {
231 return;
232 }
Beverlye91f0d02018-05-15 14:40:47 -0400233 int newRotation = RotationUtils.getExactRotation(mContext);
234 if (newRotation != mRotation) {
235 mRotation = newRotation;
Adrian Roos5b518852018-01-23 17:23:38 +0100236
237 if (mOverlay != null) {
238 updateLayoutParams();
239 updateViews();
240 }
Adrian Roos5b518852018-01-23 17:23:38 +0100241 }
242 }
243
244 private void updateViews() {
245 View topLeft = mOverlay.findViewById(R.id.left);
246 View topRight = mOverlay.findViewById(R.id.right);
247 View bottomLeft = mBottomOverlay.findViewById(R.id.left);
248 View bottomRight = mBottomOverlay.findViewById(R.id.right);
Beverlye91f0d02018-05-15 14:40:47 -0400249
250 if (mRotation == RotationUtils.ROTATION_NONE) {
251 updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
252 updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
253 updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
254 updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
255 } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
256 updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
257 updateView(topRight, Gravity.BOTTOM | Gravity.LEFT, 270);
258 updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);;
259 updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
260 } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
261 updateView(topLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
262 updateView(topRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
263 updateView(bottomLeft, Gravity.TOP | Gravity.LEFT, 0);
264 updateView(bottomRight, Gravity.TOP | Gravity.RIGHT, 90);
265 } else if (mRotation == RotationUtils.ROTATION_SEASCAPE) {
266 updateView(topLeft, Gravity.BOTTOM | Gravity.RIGHT, 180);
267 updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
268 updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
269 updateView(bottomRight, Gravity.TOP | Gravity.LEFT, 0);
Adrian Roos5b518852018-01-23 17:23:38 +0100270 }
Adrian Roos5b518852018-01-23 17:23:38 +0100271
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200272 mCutoutTop.setRotation(mRotation);
273 mCutoutBottom.setRotation(mRotation);
274
Adrian Roos5b518852018-01-23 17:23:38 +0100275 updateWindowVisibilities();
276 }
277
278 private void updateView(View v, int gravity, int rotation) {
279 ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity;
280 v.setRotation(rotation);
281 }
282
283 private void updateWindowVisibilities() {
284 updateWindowVisibility(mOverlay);
285 updateWindowVisibility(mBottomOverlay);
286 }
287
288 private void updateWindowVisibility(View overlay) {
289 boolean visibleForCutout = shouldDrawCutout()
290 && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE;
Beverlya5f7a302018-04-25 09:19:05 -0400291 boolean visibleForRoundedCorners = hasRoundedCorners();
Adrian Roos5b518852018-01-23 17:23:38 +0100292 overlay.setVisibility(visibleForCutout || visibleForRoundedCorners
293 ? View.VISIBLE : View.GONE);
294 }
295
Beverlya5f7a302018-04-25 09:19:05 -0400296 private boolean hasRoundedCorners() {
297 return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0;
298 }
299
Adrian Roos5b518852018-01-23 17:23:38 +0100300 private boolean shouldDrawCutout() {
Adrian Roosc41b32b2018-04-12 10:13:48 +0200301 return shouldDrawCutout(mContext);
302 }
303
304 static boolean shouldDrawCutout(Context context) {
305 return context.getResources().getBoolean(
Adrian Roos5b518852018-01-23 17:23:38 +0100306 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
307 }
308
309 private void setupPadding(int padding) {
310 // Add some padding to all the content near the edge of the screen.
311 StatusBar sb = getComponent(StatusBar.class);
312 View statusBar = (sb != null ? sb.getStatusBarWindow() : null);
313 if (statusBar != null) {
314 TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING,
315 padding, FLAG_END);
316
317 FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar);
318 fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
319 new TunablePaddingTagListener(padding, R.id.status_bar));
320 fragmentHostManager.addTagListener(QS.TAG,
321 new TunablePaddingTagListener(padding, R.id.header));
322 }
323 }
324
325 @VisibleForTesting
326 WindowManager.LayoutParams getWindowLayoutParams() {
327 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
328 ViewGroup.LayoutParams.MATCH_PARENT,
329 LayoutParams.WRAP_CONTENT,
330 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
Evan Lairdb0506ca2018-03-15 10:34:31 -0400331 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
Adrian Roos5b518852018-01-23 17:23:38 +0100332 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
333 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
334 | WindowManager.LayoutParams.FLAG_SLIPPERY
335 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
336 PixelFormat.TRANSLUCENT);
Robert Carr772e8bc2018-03-14 11:51:23 -0700337 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
338 | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
339
Adrian Roos56d1a2c2018-03-08 23:22:19 +0100340 if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
341 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
342 }
Robert Carr772e8bc2018-03-14 11:51:23 -0700343
Adrian Roos5b518852018-01-23 17:23:38 +0100344 lp.setTitle("ScreenDecorOverlay");
Beverlye91f0d02018-05-15 14:40:47 -0400345 if (mRotation == RotationUtils.ROTATION_SEASCAPE
346 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
347 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
348 } else {
349 lp.gravity = Gravity.TOP | Gravity.LEFT;
350 }
Adrian Roos5b518852018-01-23 17:23:38 +0100351 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
Beverlye91f0d02018-05-15 14:40:47 -0400352 if (isLandscape(mRotation)) {
Adrian Roos5b518852018-01-23 17:23:38 +0100353 lp.width = WRAP_CONTENT;
354 lp.height = MATCH_PARENT;
355 }
356 return lp;
357 }
358
359 private WindowManager.LayoutParams getBottomLayoutParams() {
360 WindowManager.LayoutParams lp = getWindowLayoutParams();
361 lp.setTitle("ScreenDecorOverlayBottom");
Beverlye91f0d02018-05-15 14:40:47 -0400362 if (mRotation == RotationUtils.ROTATION_SEASCAPE
363 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
364 lp.gravity = Gravity.TOP | Gravity.LEFT;
365 } else {
366 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
367 }
Adrian Roos5b518852018-01-23 17:23:38 +0100368 return lp;
369 }
370
371 private void updateLayoutParams() {
372 mWindowManager.updateViewLayout(mOverlay, getWindowLayoutParams());
373 mWindowManager.updateViewLayout(mBottomOverlay, getBottomLayoutParams());
374 }
375
376 @Override
377 public void onTuningChanged(String key, String newValue) {
378 if (mOverlay == null) return;
379 if (SIZE.equals(key)) {
380 int size = mRoundedDefault;
Beverlya5f7a302018-04-25 09:19:05 -0400381 int sizeTop = mRoundedDefaultTop;
382 int sizeBottom = mRoundedDefaultBottom;
383 if (newValue != null) {
384 try {
385 size = (int) (Integer.parseInt(newValue) * mDensity);
386 } catch (Exception e) {
387 }
Adrian Roos5b518852018-01-23 17:23:38 +0100388 }
Beverlya5f7a302018-04-25 09:19:05 -0400389
390 if (sizeTop == 0) {
391 sizeTop = size;
392 }
393 if (sizeBottom == 0) {
394 sizeBottom = size;
395 }
396
397 setSize(mOverlay.findViewById(R.id.left), sizeTop);
398 setSize(mOverlay.findViewById(R.id.right), sizeTop);
399 setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom);
400 setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom);
Adrian Roos5b518852018-01-23 17:23:38 +0100401 }
402 }
403
404 private void setSize(View view, int pixelSize) {
405 LayoutParams params = view.getLayoutParams();
406 params.width = pixelSize;
407 params.height = pixelSize;
408 view.setLayoutParams(params);
409 }
410
411 @VisibleForTesting
412 static class TunablePaddingTagListener implements FragmentListener {
413
414 private final int mPadding;
415 private final int mId;
416 private TunablePadding mTunablePadding;
417
418 public TunablePaddingTagListener(int padding, int id) {
419 mPadding = padding;
420 mId = id;
421 }
422
423 @Override
424 public void onFragmentViewCreated(String tag, Fragment fragment) {
425 if (mTunablePadding != null) {
426 mTunablePadding.destroy();
427 }
428 View view = fragment.getView();
429 if (mId != 0) {
430 view = view.findViewById(mId);
431 }
432 mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding,
433 FLAG_START | FLAG_END);
434 }
435 }
436
Evan Lairdb0506ca2018-03-15 10:34:31 -0400437 public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener,
438 RegionInterceptableView {
Adrian Roos5b518852018-01-23 17:23:38 +0100439
440 private final DisplayInfo mInfo = new DisplayInfo();
441 private final Paint mPaint = new Paint();
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100442 private final Region mBounds = new Region();
Adrian Roos5b518852018-01-23 17:23:38 +0100443 private final Rect mBoundingRect = new Rect();
444 private final Path mBoundingPath = new Path();
445 private final int[] mLocation = new int[2];
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200446 private final boolean mInitialStart;
Adrian Roos5b518852018-01-23 17:23:38 +0100447 private final Runnable mVisibilityChangedListener;
Adrian Roos28c25e22018-05-31 18:07:28 +0200448 private final ScreenDecorations mDecorations;
Adrian Roos8b29a842018-05-31 14:14:13 +0200449 private int mColor = Color.BLACK;
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200450 private boolean mStart;
451 private int mRotation;
Adrian Roos5b518852018-01-23 17:23:38 +0100452
453 public DisplayCutoutView(Context context, boolean start,
Adrian Roos28c25e22018-05-31 18:07:28 +0200454 Runnable visibilityChangedListener, ScreenDecorations decorations) {
Adrian Roos5b518852018-01-23 17:23:38 +0100455 super(context);
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200456 mInitialStart = start;
Adrian Roos5b518852018-01-23 17:23:38 +0100457 mVisibilityChangedListener = visibilityChangedListener;
Adrian Roos28c25e22018-05-31 18:07:28 +0200458 mDecorations = decorations;
Adrian Roos5b518852018-01-23 17:23:38 +0100459 setId(R.id.display_cutout);
460 }
461
Adrian Roos8b29a842018-05-31 14:14:13 +0200462 public void setColor(int color) {
463 mColor = color;
464 invalidate();
465 }
466
Adrian Roos5b518852018-01-23 17:23:38 +0100467 @Override
468 protected void onAttachedToWindow() {
469 super.onAttachedToWindow();
470 mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
471 getHandler());
472 update();
473 }
474
475 @Override
476 protected void onDetachedFromWindow() {
477 super.onDetachedFromWindow();
478 mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
479 }
480
481 @Override
482 protected void onDraw(Canvas canvas) {
483 super.onDraw(canvas);
484 getLocationOnScreen(mLocation);
485 canvas.translate(-mLocation[0], -mLocation[1]);
486 if (!mBoundingPath.isEmpty()) {
Adrian Roos8b29a842018-05-31 14:14:13 +0200487 mPaint.setColor(mColor);
Adrian Roos5b518852018-01-23 17:23:38 +0100488 mPaint.setStyle(Paint.Style.FILL);
Adrian Roos51072a82018-04-10 15:17:08 -0700489 mPaint.setAntiAlias(true);
Adrian Roos5b518852018-01-23 17:23:38 +0100490 canvas.drawPath(mBoundingPath, mPaint);
491 }
492 }
493
494 @Override
495 public void onDisplayAdded(int displayId) {
496 }
497
498 @Override
499 public void onDisplayRemoved(int displayId) {
500 }
501
502 @Override
503 public void onDisplayChanged(int displayId) {
504 if (displayId == getDisplay().getDisplayId()) {
505 update();
506 }
507 }
508
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200509 public void setRotation(int rotation) {
510 mRotation = rotation;
511 update();
512 }
513
514 private boolean isStart() {
515 final boolean flipped = (mRotation == RotationUtils.ROTATION_SEASCAPE
516 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN);
517 return flipped ? !mInitialStart : mInitialStart;
518 }
519
Adrian Roos5b518852018-01-23 17:23:38 +0100520 private void update() {
Adrian Roos28c25e22018-05-31 18:07:28 +0200521 if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) {
Adrian Roosbeeb7f42018-05-31 19:54:53 +0200522 return;
523 }
Adrian Roos28c25e22018-05-31 18:07:28 +0200524 mStart = isStart();
Adrian Roos5b518852018-01-23 17:23:38 +0100525 requestLayout();
526 getDisplay().getDisplayInfo(mInfo);
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100527 mBounds.setEmpty();
Adrian Roos5b518852018-01-23 17:23:38 +0100528 mBoundingRect.setEmpty();
529 mBoundingPath.reset();
530 int newVisible;
Adrian Roosc41b32b2018-04-12 10:13:48 +0200531 if (shouldDrawCutout(getContext()) && hasCutout()) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100532 mBounds.set(mInfo.displayCutout.getBounds());
533 localBounds(mBoundingRect);
Adrian Roos51072a82018-04-10 15:17:08 -0700534 updateBoundingPath();
Adrian Roos1b028282018-03-14 14:43:03 +0100535 invalidate();
Adrian Roos5b518852018-01-23 17:23:38 +0100536 newVisible = VISIBLE;
537 } else {
538 newVisible = GONE;
539 }
540 if (newVisible != getVisibility()) {
541 setVisibility(newVisible);
542 mVisibilityChangedListener.run();
543 }
544 }
545
Adrian Roos51072a82018-04-10 15:17:08 -0700546 private void updateBoundingPath() {
547 int lw = mInfo.logicalWidth;
548 int lh = mInfo.logicalHeight;
549
550 boolean flipped = mInfo.rotation == ROTATION_90 || mInfo.rotation == ROTATION_270;
551
552 int dw = flipped ? lh : lw;
553 int dh = flipped ? lw : lh;
554
Adrian Roosa99f5d62018-04-16 16:03:04 +0200555 mBoundingPath.set(DisplayCutout.pathFromResources(getResources(), dw, dh));
Adrian Roos51072a82018-04-10 15:17:08 -0700556 Matrix m = new Matrix();
557 transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
558 mBoundingPath.transform(m);
559 }
560
561 private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation,
562 @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
563 switch (rotation) {
564 case ROTATION_0:
565 out.reset();
566 break;
567 case ROTATION_90:
568 out.setRotate(270);
569 out.postTranslate(0, physicalWidth);
570 break;
571 case ROTATION_180:
572 out.setRotate(180);
573 out.postTranslate(physicalWidth, physicalHeight);
574 break;
575 case ROTATION_270:
576 out.setRotate(90);
577 out.postTranslate(physicalHeight, 0);
578 break;
579 default:
580 throw new IllegalArgumentException("Unknown rotation: " + rotation);
581 }
582 }
583
Adrian Roos5b518852018-01-23 17:23:38 +0100584 private boolean hasCutout() {
Adrian Roos24264212018-02-19 16:26:15 +0100585 final DisplayCutout displayCutout = mInfo.displayCutout;
586 if (displayCutout == null) {
Adrian Roos5b518852018-01-23 17:23:38 +0100587 return false;
588 }
Adrian Roos5b518852018-01-23 17:23:38 +0100589 if (mStart) {
590 return displayCutout.getSafeInsetLeft() > 0
591 || displayCutout.getSafeInsetTop() > 0;
592 } else {
593 return displayCutout.getSafeInsetRight() > 0
594 || displayCutout.getSafeInsetBottom() > 0;
595 }
596 }
597
598 @Override
599 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100600 if (mBounds.isEmpty()) {
Adrian Roos5b518852018-01-23 17:23:38 +0100601 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
602 return;
603 }
604 setMeasuredDimension(
605 resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
606 resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
607 }
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100608
Jorim Jaggi0d4a9952018-06-06 17:22:56 +0200609 public static void boundsFromDirection(DisplayCutout displayCutout, int gravity,
610 Rect out) {
611 Region bounds = boundsFromDirection(displayCutout, gravity);
612 out.set(bounds.getBounds());
613 bounds.recycle();
614 }
615
616 public static Region boundsFromDirection(DisplayCutout displayCutout, int gravity) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100617 Region bounds = displayCutout.getBounds();
618 switch (gravity) {
619 case Gravity.TOP:
620 bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(),
621 Region.Op.INTERSECT);
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100622 break;
623 case Gravity.LEFT:
624 bounds.op(0, 0, displayCutout.getSafeInsetLeft(), Integer.MAX_VALUE,
625 Region.Op.INTERSECT);
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100626 break;
627 case Gravity.BOTTOM:
628 bounds.op(0, displayCutout.getSafeInsetTop() + 1, Integer.MAX_VALUE,
629 Integer.MAX_VALUE, Region.Op.INTERSECT);
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100630 break;
631 case Gravity.RIGHT:
632 bounds.op(displayCutout.getSafeInsetLeft() + 1, 0, Integer.MAX_VALUE,
633 Integer.MAX_VALUE, Region.Op.INTERSECT);
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100634 break;
635 }
Jorim Jaggi0d4a9952018-06-06 17:22:56 +0200636 return bounds;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100637 }
638
639 private void localBounds(Rect out) {
640 final DisplayCutout displayCutout = mInfo.displayCutout;
641
642 if (mStart) {
643 if (displayCutout.getSafeInsetLeft() > 0) {
644 boundsFromDirection(displayCutout, Gravity.LEFT, out);
645 } else if (displayCutout.getSafeInsetTop() > 0) {
646 boundsFromDirection(displayCutout, Gravity.TOP, out);
647 }
648 } else {
649 if (displayCutout.getSafeInsetRight() > 0) {
650 boundsFromDirection(displayCutout, Gravity.RIGHT, out);
651 } else if (displayCutout.getSafeInsetBottom() > 0) {
652 boundsFromDirection(displayCutout, Gravity.BOTTOM, out);
653 }
654 }
655 }
Evan Lairdb0506ca2018-03-15 10:34:31 -0400656
657 @Override
658 public boolean shouldInterceptTouch() {
659 return mInfo.displayCutout != null && getVisibility() == VISIBLE;
660 }
661
662 @Override
663 public Region getInterceptRegion() {
664 if (mInfo.displayCutout == null) {
665 return null;
666 }
667
Adrian Roos134e1cb2018-05-16 17:04:29 +0200668 View rootView = getRootView();
669 Region cutoutBounds = mInfo.displayCutout.getBounds();
670
671 // Transform to window's coordinate space
672 rootView.getLocationOnScreen(mLocation);
673 cutoutBounds.translate(-mLocation[0], -mLocation[1]);
674
675 // Intersect with window's frame
676 cutoutBounds.op(rootView.getLeft(), rootView.getTop(), rootView.getRight(),
677 rootView.getBottom(), Region.Op.INTERSECT);
678
679 return cutoutBounds;
Evan Lairdb0506ca2018-03-15 10:34:31 -0400680 }
Adrian Roos5b518852018-01-23 17:23:38 +0100681 }
Beverlye91f0d02018-05-15 14:40:47 -0400682
683 private boolean isLandscape(int rotation) {
684 return rotation == RotationUtils.ROTATION_LANDSCAPE || rotation ==
685 RotationUtils.ROTATION_SEASCAPE;
686 }
Adrian Roos28c25e22018-05-31 18:07:28 +0200687
688 /**
689 * A pre-draw listener, that cancels the draw and restarts the traversal with the updated
690 * window attributes.
691 */
692 private class RestartingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
693
694 private final View mView;
695
696 private RestartingPreDrawListener(View view) {
697 mView = view;
698 }
699
700 @Override
701 public boolean onPreDraw() {
702 mPendingRotationChange = false;
703 mView.getViewTreeObserver().removeOnPreDrawListener(this);
704 // This changes the window attributes - we need to restart the traversal for them to
705 // take effect.
706 updateOrientation();
707 mView.invalidate();
708 return false;
709 }
710 }
Adrian Roos5b518852018-01-23 17:23:38 +0100711}