blob: 049765f2824bd44e4333ed0f7b4ba3ba992c3b5d [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;
Aurimas Liutikasb8616dc2018-04-17 09:50:46 -070044import androidx.annotation.VisibleForTesting;
Adrian Roos5b518852018-01-23 17:23:38 +010045import 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;
55import android.view.WindowManager;
56import android.widget.FrameLayout;
57import android.widget.ImageView;
58
Evan Lairdb0506ca2018-03-15 10:34:31 -040059import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
Adrian Roos5b518852018-01-23 17:23:38 +010060import com.android.systemui.fragments.FragmentHostManager;
61import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
62import com.android.systemui.plugins.qs.QS;
63import com.android.systemui.qs.SecureSetting;
64import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
65import com.android.systemui.statusbar.phone.StatusBar;
66import com.android.systemui.tuner.TunablePadding;
67import com.android.systemui.tuner.TunerService;
68import com.android.systemui.tuner.TunerService.Tunable;
Beverlye91f0d02018-05-15 14:40:47 -040069import com.android.systemui.util.leak.RotationUtils;
Adrian Roos5b518852018-01-23 17:23:38 +010070
71/**
72 * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
73 * for antialiasing and emulation purposes.
74 */
75public class ScreenDecorations extends SystemUI implements Tunable {
76 public static final String SIZE = "sysui_rounded_size";
77 public static final String PADDING = "sysui_rounded_content_padding";
Adrian Roos56d1a2c2018-03-08 23:22:19 +010078 private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
79 SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
Adrian Roos5b518852018-01-23 17:23:38 +010080
Beverlye91f0d02018-05-15 14:40:47 -040081 private DisplayManager mDisplayManager;
82 private DisplayManager.DisplayListener mDisplayListener;
83
Adrian Roos5b518852018-01-23 17:23:38 +010084 private int mRoundedDefault;
Beverlya5f7a302018-04-25 09:19:05 -040085 private int mRoundedDefaultTop;
86 private int mRoundedDefaultBottom;
Adrian Roos5b518852018-01-23 17:23:38 +010087 private View mOverlay;
88 private View mBottomOverlay;
89 private float mDensity;
90 private WindowManager mWindowManager;
Beverlye91f0d02018-05-15 14:40:47 -040091 private int mRotation;
Adrian Roos5b518852018-01-23 17:23:38 +010092
93 @Override
94 public void start() {
95 mWindowManager = mContext.getSystemService(WindowManager.class);
96 mRoundedDefault = mContext.getResources().getDimensionPixelSize(
97 R.dimen.rounded_corner_radius);
Beverlya5f7a302018-04-25 09:19:05 -040098 mRoundedDefaultTop = mContext.getResources().getDimensionPixelSize(
99 R.dimen.rounded_corner_radius_top);
100 mRoundedDefaultBottom = mContext.getResources().getDimensionPixelSize(
101 R.dimen.rounded_corner_radius_bottom);
102 if (hasRoundedCorners() || shouldDrawCutout()) {
Adrian Roos5b518852018-01-23 17:23:38 +0100103 setupDecorations();
104 }
Beverlya5f7a302018-04-25 09:19:05 -0400105
Adrian Roos5b518852018-01-23 17:23:38 +0100106 int padding = mContext.getResources().getDimensionPixelSize(
107 R.dimen.rounded_corner_content_padding);
108 if (padding != 0) {
109 setupPadding(padding);
110 }
Beverlye91f0d02018-05-15 14:40:47 -0400111
112 mDisplayListener = new DisplayManager.DisplayListener() {
113 @Override
114 public void onDisplayAdded(int displayId) {
115 // do nothing
116 }
117
118 @Override
119 public void onDisplayRemoved(int displayId) {
120 // do nothing
121 }
122
123 @Override
124 public void onDisplayChanged(int displayId) {
125 updateOrientation();
126 }
127 };
128
129 mRotation = -1;
130 mDisplayManager = (DisplayManager) mContext.getSystemService(
131 Context.DISPLAY_SERVICE);
132 mDisplayManager.registerDisplayListener(mDisplayListener, null);
Adrian Roos5b518852018-01-23 17:23:38 +0100133 }
134
135 private void setupDecorations() {
136 mOverlay = LayoutInflater.from(mContext)
137 .inflate(R.layout.rounded_corners, null);
Adrian Roos8b29a842018-05-31 14:14:13 +0200138 DisplayCutoutView cutoutTop = new DisplayCutoutView(mContext, true,
139 this::updateWindowVisibilities);
140 ((ViewGroup)mOverlay).addView(cutoutTop);
Adrian Roos5b518852018-01-23 17:23:38 +0100141 mBottomOverlay = LayoutInflater.from(mContext)
142 .inflate(R.layout.rounded_corners, null);
Adrian Roos8b29a842018-05-31 14:14:13 +0200143 DisplayCutoutView cutoutBottom = new DisplayCutoutView(mContext, false,
144 this::updateWindowVisibilities);
145 ((ViewGroup)mBottomOverlay).addView(cutoutBottom);
Adrian Roos5b518852018-01-23 17:23:38 +0100146
147 mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
148 mOverlay.setAlpha(0);
149
150 mBottomOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
151 mBottomOverlay.setAlpha(0);
152
153 updateViews();
154
155 mWindowManager.addView(mOverlay, getWindowLayoutParams());
156 mWindowManager.addView(mBottomOverlay, getBottomLayoutParams());
157
158 DisplayMetrics metrics = new DisplayMetrics();
159 mWindowManager.getDefaultDisplay().getMetrics(metrics);
160 mDensity = metrics.density;
161
162 Dependency.get(TunerService.class).addTunable(this, SIZE);
163
164 // Watch color inversion and invert the overlay as needed.
165 SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER),
166 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
167 @Override
168 protected void handleValueChanged(int value, boolean observedChange) {
169 int tint = value != 0 ? Color.WHITE : Color.BLACK;
170 ColorStateList tintList = ColorStateList.valueOf(tint);
171 ((ImageView) mOverlay.findViewById(R.id.left)).setImageTintList(tintList);
172 ((ImageView) mOverlay.findViewById(R.id.right)).setImageTintList(tintList);
173 ((ImageView) mBottomOverlay.findViewById(R.id.left)).setImageTintList(tintList);
174 ((ImageView) mBottomOverlay.findViewById(R.id.right)).setImageTintList(tintList);
Adrian Roos8b29a842018-05-31 14:14:13 +0200175 cutoutTop.setColor(tint);
176 cutoutBottom.setColor(tint);
Adrian Roos5b518852018-01-23 17:23:38 +0100177 }
178 };
179 setting.setListening(true);
180 setting.onChange(false);
181
182 mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
183 @Override
184 public void onLayoutChange(View v, int left, int top, int right, int bottom,
185 int oldLeft,
186 int oldTop, int oldRight, int oldBottom) {
187 mOverlay.removeOnLayoutChangeListener(this);
188 mOverlay.animate()
189 .alpha(1)
190 .setDuration(1000)
191 .start();
192 mBottomOverlay.animate()
193 .alpha(1)
194 .setDuration(1000)
195 .start();
196 }
197 });
198 }
199
200 @Override
201 protected void onConfigurationChanged(Configuration newConfig) {
Beverlye91f0d02018-05-15 14:40:47 -0400202 updateOrientation();
Beverly5c2b48a2018-05-24 09:25:45 -0400203 if (shouldDrawCutout() && mOverlay == null) {
204 setupDecorations();
205 }
Beverlye91f0d02018-05-15 14:40:47 -0400206 }
207
208 protected void updateOrientation() {
209 int newRotation = RotationUtils.getExactRotation(mContext);
210 if (newRotation != mRotation) {
211 mRotation = newRotation;
Adrian Roos5b518852018-01-23 17:23:38 +0100212
213 if (mOverlay != null) {
214 updateLayoutParams();
215 updateViews();
216 }
Adrian Roos5b518852018-01-23 17:23:38 +0100217 }
218 }
219
220 private void updateViews() {
221 View topLeft = mOverlay.findViewById(R.id.left);
222 View topRight = mOverlay.findViewById(R.id.right);
223 View bottomLeft = mBottomOverlay.findViewById(R.id.left);
224 View bottomRight = mBottomOverlay.findViewById(R.id.right);
Beverlye91f0d02018-05-15 14:40:47 -0400225
226 if (mRotation == RotationUtils.ROTATION_NONE) {
227 updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
228 updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
229 updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
230 updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
231 } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
232 updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
233 updateView(topRight, Gravity.BOTTOM | Gravity.LEFT, 270);
234 updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);;
235 updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
236 } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
237 updateView(topLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
238 updateView(topRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
239 updateView(bottomLeft, Gravity.TOP | Gravity.LEFT, 0);
240 updateView(bottomRight, Gravity.TOP | Gravity.RIGHT, 90);
241 } else if (mRotation == RotationUtils.ROTATION_SEASCAPE) {
242 updateView(topLeft, Gravity.BOTTOM | Gravity.RIGHT, 180);
243 updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
244 updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
245 updateView(bottomRight, Gravity.TOP | Gravity.LEFT, 0);
Adrian Roos5b518852018-01-23 17:23:38 +0100246 }
Adrian Roos5b518852018-01-23 17:23:38 +0100247
248 updateWindowVisibilities();
249 }
250
251 private void updateView(View v, int gravity, int rotation) {
252 ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity;
253 v.setRotation(rotation);
254 }
255
256 private void updateWindowVisibilities() {
257 updateWindowVisibility(mOverlay);
258 updateWindowVisibility(mBottomOverlay);
259 }
260
261 private void updateWindowVisibility(View overlay) {
262 boolean visibleForCutout = shouldDrawCutout()
263 && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE;
Beverlya5f7a302018-04-25 09:19:05 -0400264 boolean visibleForRoundedCorners = hasRoundedCorners();
Adrian Roos5b518852018-01-23 17:23:38 +0100265 overlay.setVisibility(visibleForCutout || visibleForRoundedCorners
266 ? View.VISIBLE : View.GONE);
267 }
268
Beverlya5f7a302018-04-25 09:19:05 -0400269 private boolean hasRoundedCorners() {
270 return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0;
271 }
272
Adrian Roos5b518852018-01-23 17:23:38 +0100273 private boolean shouldDrawCutout() {
Adrian Roosc41b32b2018-04-12 10:13:48 +0200274 return shouldDrawCutout(mContext);
275 }
276
277 static boolean shouldDrawCutout(Context context) {
278 return context.getResources().getBoolean(
Adrian Roos5b518852018-01-23 17:23:38 +0100279 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
280 }
281
282 private void setupPadding(int padding) {
283 // Add some padding to all the content near the edge of the screen.
284 StatusBar sb = getComponent(StatusBar.class);
285 View statusBar = (sb != null ? sb.getStatusBarWindow() : null);
286 if (statusBar != null) {
287 TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING,
288 padding, FLAG_END);
289
290 FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar);
291 fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
292 new TunablePaddingTagListener(padding, R.id.status_bar));
293 fragmentHostManager.addTagListener(QS.TAG,
294 new TunablePaddingTagListener(padding, R.id.header));
295 }
296 }
297
298 @VisibleForTesting
299 WindowManager.LayoutParams getWindowLayoutParams() {
300 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
301 ViewGroup.LayoutParams.MATCH_PARENT,
302 LayoutParams.WRAP_CONTENT,
303 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
Evan Lairdb0506ca2018-03-15 10:34:31 -0400304 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
Adrian Roos5b518852018-01-23 17:23:38 +0100305 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
306 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
307 | WindowManager.LayoutParams.FLAG_SLIPPERY
308 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
309 PixelFormat.TRANSLUCENT);
Robert Carr772e8bc2018-03-14 11:51:23 -0700310 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
311 | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
312
Adrian Roos56d1a2c2018-03-08 23:22:19 +0100313 if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
314 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
315 }
Robert Carr772e8bc2018-03-14 11:51:23 -0700316
Adrian Roos5b518852018-01-23 17:23:38 +0100317 lp.setTitle("ScreenDecorOverlay");
Beverlye91f0d02018-05-15 14:40:47 -0400318 if (mRotation == RotationUtils.ROTATION_SEASCAPE
319 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
320 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
321 } else {
322 lp.gravity = Gravity.TOP | Gravity.LEFT;
323 }
Adrian Roos5b518852018-01-23 17:23:38 +0100324 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
Beverlye91f0d02018-05-15 14:40:47 -0400325 if (isLandscape(mRotation)) {
Adrian Roos5b518852018-01-23 17:23:38 +0100326 lp.width = WRAP_CONTENT;
327 lp.height = MATCH_PARENT;
328 }
329 return lp;
330 }
331
332 private WindowManager.LayoutParams getBottomLayoutParams() {
333 WindowManager.LayoutParams lp = getWindowLayoutParams();
334 lp.setTitle("ScreenDecorOverlayBottom");
Beverlye91f0d02018-05-15 14:40:47 -0400335 if (mRotation == RotationUtils.ROTATION_SEASCAPE
336 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
337 lp.gravity = Gravity.TOP | Gravity.LEFT;
338 } else {
339 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
340 }
Adrian Roos5b518852018-01-23 17:23:38 +0100341 return lp;
342 }
343
344 private void updateLayoutParams() {
345 mWindowManager.updateViewLayout(mOverlay, getWindowLayoutParams());
346 mWindowManager.updateViewLayout(mBottomOverlay, getBottomLayoutParams());
347 }
348
349 @Override
350 public void onTuningChanged(String key, String newValue) {
351 if (mOverlay == null) return;
352 if (SIZE.equals(key)) {
353 int size = mRoundedDefault;
Beverlya5f7a302018-04-25 09:19:05 -0400354 int sizeTop = mRoundedDefaultTop;
355 int sizeBottom = mRoundedDefaultBottom;
356 if (newValue != null) {
357 try {
358 size = (int) (Integer.parseInt(newValue) * mDensity);
359 } catch (Exception e) {
360 }
Adrian Roos5b518852018-01-23 17:23:38 +0100361 }
Beverlya5f7a302018-04-25 09:19:05 -0400362
363 if (sizeTop == 0) {
364 sizeTop = size;
365 }
366 if (sizeBottom == 0) {
367 sizeBottom = size;
368 }
369
370 setSize(mOverlay.findViewById(R.id.left), sizeTop);
371 setSize(mOverlay.findViewById(R.id.right), sizeTop);
372 setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom);
373 setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom);
Adrian Roos5b518852018-01-23 17:23:38 +0100374 }
375 }
376
377 private void setSize(View view, int pixelSize) {
378 LayoutParams params = view.getLayoutParams();
379 params.width = pixelSize;
380 params.height = pixelSize;
381 view.setLayoutParams(params);
382 }
383
384 @VisibleForTesting
385 static class TunablePaddingTagListener implements FragmentListener {
386
387 private final int mPadding;
388 private final int mId;
389 private TunablePadding mTunablePadding;
390
391 public TunablePaddingTagListener(int padding, int id) {
392 mPadding = padding;
393 mId = id;
394 }
395
396 @Override
397 public void onFragmentViewCreated(String tag, Fragment fragment) {
398 if (mTunablePadding != null) {
399 mTunablePadding.destroy();
400 }
401 View view = fragment.getView();
402 if (mId != 0) {
403 view = view.findViewById(mId);
404 }
405 mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding,
406 FLAG_START | FLAG_END);
407 }
408 }
409
Evan Lairdb0506ca2018-03-15 10:34:31 -0400410 public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener,
411 RegionInterceptableView {
Adrian Roos5b518852018-01-23 17:23:38 +0100412
413 private final DisplayInfo mInfo = new DisplayInfo();
414 private final Paint mPaint = new Paint();
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100415 private final Region mBounds = new Region();
Adrian Roos5b518852018-01-23 17:23:38 +0100416 private final Rect mBoundingRect = new Rect();
417 private final Path mBoundingPath = new Path();
418 private final int[] mLocation = new int[2];
419 private final boolean mStart;
420 private final Runnable mVisibilityChangedListener;
Adrian Roos8b29a842018-05-31 14:14:13 +0200421 private int mColor = Color.BLACK;
Adrian Roos5b518852018-01-23 17:23:38 +0100422
423 public DisplayCutoutView(Context context, boolean start,
424 Runnable visibilityChangedListener) {
425 super(context);
426 mStart = start;
427 mVisibilityChangedListener = visibilityChangedListener;
428 setId(R.id.display_cutout);
429 }
430
Adrian Roos8b29a842018-05-31 14:14:13 +0200431 public void setColor(int color) {
432 mColor = color;
433 invalidate();
434 }
435
Adrian Roos5b518852018-01-23 17:23:38 +0100436 @Override
437 protected void onAttachedToWindow() {
438 super.onAttachedToWindow();
439 mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
440 getHandler());
441 update();
442 }
443
444 @Override
445 protected void onDetachedFromWindow() {
446 super.onDetachedFromWindow();
447 mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
448 }
449
450 @Override
451 protected void onDraw(Canvas canvas) {
452 super.onDraw(canvas);
453 getLocationOnScreen(mLocation);
454 canvas.translate(-mLocation[0], -mLocation[1]);
455 if (!mBoundingPath.isEmpty()) {
Adrian Roos8b29a842018-05-31 14:14:13 +0200456 mPaint.setColor(mColor);
Adrian Roos5b518852018-01-23 17:23:38 +0100457 mPaint.setStyle(Paint.Style.FILL);
Adrian Roos51072a82018-04-10 15:17:08 -0700458 mPaint.setAntiAlias(true);
Adrian Roos5b518852018-01-23 17:23:38 +0100459 canvas.drawPath(mBoundingPath, mPaint);
460 }
461 }
462
463 @Override
464 public void onDisplayAdded(int displayId) {
465 }
466
467 @Override
468 public void onDisplayRemoved(int displayId) {
469 }
470
471 @Override
472 public void onDisplayChanged(int displayId) {
473 if (displayId == getDisplay().getDisplayId()) {
474 update();
475 }
476 }
477
478 private void update() {
479 requestLayout();
480 getDisplay().getDisplayInfo(mInfo);
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100481 mBounds.setEmpty();
Adrian Roos5b518852018-01-23 17:23:38 +0100482 mBoundingRect.setEmpty();
483 mBoundingPath.reset();
484 int newVisible;
Adrian Roosc41b32b2018-04-12 10:13:48 +0200485 if (shouldDrawCutout(getContext()) && hasCutout()) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100486 mBounds.set(mInfo.displayCutout.getBounds());
487 localBounds(mBoundingRect);
Adrian Roos51072a82018-04-10 15:17:08 -0700488 updateBoundingPath();
Adrian Roos1b028282018-03-14 14:43:03 +0100489 invalidate();
Adrian Roos5b518852018-01-23 17:23:38 +0100490 newVisible = VISIBLE;
491 } else {
492 newVisible = GONE;
493 }
494 if (newVisible != getVisibility()) {
495 setVisibility(newVisible);
496 mVisibilityChangedListener.run();
497 }
498 }
499
Adrian Roos51072a82018-04-10 15:17:08 -0700500 private void updateBoundingPath() {
501 int lw = mInfo.logicalWidth;
502 int lh = mInfo.logicalHeight;
503
504 boolean flipped = mInfo.rotation == ROTATION_90 || mInfo.rotation == ROTATION_270;
505
506 int dw = flipped ? lh : lw;
507 int dh = flipped ? lw : lh;
508
Adrian Roosa99f5d62018-04-16 16:03:04 +0200509 mBoundingPath.set(DisplayCutout.pathFromResources(getResources(), dw, dh));
Adrian Roos51072a82018-04-10 15:17:08 -0700510 Matrix m = new Matrix();
511 transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
512 mBoundingPath.transform(m);
513 }
514
515 private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation,
516 @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
517 switch (rotation) {
518 case ROTATION_0:
519 out.reset();
520 break;
521 case ROTATION_90:
522 out.setRotate(270);
523 out.postTranslate(0, physicalWidth);
524 break;
525 case ROTATION_180:
526 out.setRotate(180);
527 out.postTranslate(physicalWidth, physicalHeight);
528 break;
529 case ROTATION_270:
530 out.setRotate(90);
531 out.postTranslate(physicalHeight, 0);
532 break;
533 default:
534 throw new IllegalArgumentException("Unknown rotation: " + rotation);
535 }
536 }
537
Adrian Roos5b518852018-01-23 17:23:38 +0100538 private boolean hasCutout() {
Adrian Roos24264212018-02-19 16:26:15 +0100539 final DisplayCutout displayCutout = mInfo.displayCutout;
540 if (displayCutout == null) {
Adrian Roos5b518852018-01-23 17:23:38 +0100541 return false;
542 }
Adrian Roos5b518852018-01-23 17:23:38 +0100543 if (mStart) {
544 return displayCutout.getSafeInsetLeft() > 0
545 || displayCutout.getSafeInsetTop() > 0;
546 } else {
547 return displayCutout.getSafeInsetRight() > 0
548 || displayCutout.getSafeInsetBottom() > 0;
549 }
550 }
551
552 @Override
553 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100554 if (mBounds.isEmpty()) {
Adrian Roos5b518852018-01-23 17:23:38 +0100555 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
556 return;
557 }
558 setMeasuredDimension(
559 resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
560 resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
561 }
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100562
563 public static void boundsFromDirection(DisplayCutout displayCutout, int gravity, Rect out) {
564 Region bounds = displayCutout.getBounds();
565 switch (gravity) {
566 case Gravity.TOP:
567 bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(),
568 Region.Op.INTERSECT);
569 out.set(bounds.getBounds());
570 break;
571 case Gravity.LEFT:
572 bounds.op(0, 0, displayCutout.getSafeInsetLeft(), Integer.MAX_VALUE,
573 Region.Op.INTERSECT);
574 out.set(bounds.getBounds());
575 break;
576 case Gravity.BOTTOM:
577 bounds.op(0, displayCutout.getSafeInsetTop() + 1, Integer.MAX_VALUE,
578 Integer.MAX_VALUE, Region.Op.INTERSECT);
579 out.set(bounds.getBounds());
580 break;
581 case Gravity.RIGHT:
582 bounds.op(displayCutout.getSafeInsetLeft() + 1, 0, Integer.MAX_VALUE,
583 Integer.MAX_VALUE, Region.Op.INTERSECT);
584 out.set(bounds.getBounds());
585 break;
586 }
587 bounds.recycle();
588 }
589
590 private void localBounds(Rect out) {
591 final DisplayCutout displayCutout = mInfo.displayCutout;
592
593 if (mStart) {
594 if (displayCutout.getSafeInsetLeft() > 0) {
595 boundsFromDirection(displayCutout, Gravity.LEFT, out);
596 } else if (displayCutout.getSafeInsetTop() > 0) {
597 boundsFromDirection(displayCutout, Gravity.TOP, out);
598 }
599 } else {
600 if (displayCutout.getSafeInsetRight() > 0) {
601 boundsFromDirection(displayCutout, Gravity.RIGHT, out);
602 } else if (displayCutout.getSafeInsetBottom() > 0) {
603 boundsFromDirection(displayCutout, Gravity.BOTTOM, out);
604 }
605 }
606 }
Evan Lairdb0506ca2018-03-15 10:34:31 -0400607
608 @Override
609 public boolean shouldInterceptTouch() {
610 return mInfo.displayCutout != null && getVisibility() == VISIBLE;
611 }
612
613 @Override
614 public Region getInterceptRegion() {
615 if (mInfo.displayCutout == null) {
616 return null;
617 }
618
Adrian Roos134e1cb2018-05-16 17:04:29 +0200619 View rootView = getRootView();
620 Region cutoutBounds = mInfo.displayCutout.getBounds();
621
622 // Transform to window's coordinate space
623 rootView.getLocationOnScreen(mLocation);
624 cutoutBounds.translate(-mLocation[0], -mLocation[1]);
625
626 // Intersect with window's frame
627 cutoutBounds.op(rootView.getLeft(), rootView.getTop(), rootView.getRight(),
628 rootView.getBottom(), Region.Op.INTERSECT);
629
630 return cutoutBounds;
Evan Lairdb0506ca2018-03-15 10:34:31 -0400631 }
Adrian Roos5b518852018-01-23 17:23:38 +0100632 }
Beverlye91f0d02018-05-15 14:40:47 -0400633
634 private boolean isLandscape(int rotation) {
635 return rotation == RotationUtils.ROTATION_LANDSCAPE || rotation ==
636 RotationUtils.ROTATION_SEASCAPE;
637 }
Adrian Roos5b518852018-01-23 17:23:38 +0100638}