blob: 922fb69b3fdcece6f46d6a2b3db32b41ac5d2509 [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
shawnlin63dfae22020-02-16 17:15:07 +080017import static android.view.Display.DEFAULT_DISPLAY;
18import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
19import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
20import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH;
21import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
22import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
Adrian Roos51072a82018-04-10 15:17:08 -070023import static android.view.Surface.ROTATION_0;
24import static android.view.Surface.ROTATION_180;
25import static android.view.Surface.ROTATION_270;
26import static android.view.Surface.ROTATION_90;
Adrian Roos5b518852018-01-23 17:23:38 +010027import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
28import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
29import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
30
Matt Casey9fedd812019-06-05 17:14:09 -040031import android.animation.Animator;
Lucas Dupin2b05ccc2020-03-04 14:15:52 -080032import android.animation.AnimatorListenerAdapter;
Lucas Dupin2b05ccc2020-03-04 14:15:52 -080033import android.animation.ValueAnimator;
Adrian Roos51072a82018-04-10 15:17:08 -070034import android.annotation.Dimension;
Evan Laird16981452020-02-21 14:33:42 -050035import android.annotation.NonNull;
Bill Lin2f67cf72020-03-24 01:16:34 +080036import android.annotation.Nullable;
Beverly374acde2018-06-13 10:49:26 -040037import android.app.ActivityManager;
Beverly374acde2018-06-13 10:49:26 -040038import android.content.BroadcastReceiver;
Adrian Roos5b518852018-01-23 17:23:38 +010039import android.content.Context;
Beverly374acde2018-06-13 10:49:26 -040040import android.content.Intent;
41import android.content.IntentFilter;
Adrian Roos5b518852018-01-23 17:23:38 +010042import android.content.res.ColorStateList;
43import android.content.res.Configuration;
Evan Laird16981452020-02-21 14:33:42 -050044import android.content.res.Resources;
Adrian Roos5b518852018-01-23 17:23:38 +010045import android.graphics.Canvas;
46import android.graphics.Color;
Adrian Roos51072a82018-04-10 15:17:08 -070047import android.graphics.Matrix;
Adrian Roos5b518852018-01-23 17:23:38 +010048import android.graphics.Paint;
49import android.graphics.Path;
50import android.graphics.PixelFormat;
51import android.graphics.Rect;
Lucas Dupin789046212020-03-10 14:13:02 -070052import android.graphics.RectF;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010053import android.graphics.Region;
Bill Lind5e79152020-02-14 15:08:13 +080054import android.graphics.drawable.VectorDrawable;
Adrian Roos5b518852018-01-23 17:23:38 +010055import android.hardware.display.DisplayManager;
Adrian Roosfaa102f2018-08-02 15:56:15 +020056import android.os.Handler;
Beverlyc5b1b562020-04-14 13:18:41 -040057import android.os.HandlerExecutor;
Adrian Roosfaa102f2018-08-02 15:56:15 +020058import android.os.HandlerThread;
Adrian Roos56d1a2c2018-03-08 23:22:19 +010059import android.os.SystemProperties;
Beverlyc5b1b562020-04-14 13:18:41 -040060import android.os.UserHandle;
Adrian Roos5b518852018-01-23 17:23:38 +010061import android.provider.Settings.Secure;
Adrian Roos5b518852018-01-23 17:23:38 +010062import android.util.DisplayMetrics;
Adrian Roosfaa102f2018-08-02 15:56:15 +020063import android.util.Log;
Adrian Roos5b518852018-01-23 17:23:38 +010064import android.view.DisplayCutout;
shawnlin63dfae22020-02-16 17:15:07 +080065import android.view.DisplayCutout.BoundsPosition;
Adrian Roos5b518852018-01-23 17:23:38 +010066import android.view.DisplayInfo;
67import android.view.Gravity;
68import android.view.LayoutInflater;
Adrian Roos51072a82018-04-10 15:17:08 -070069import android.view.Surface;
Adrian Roos5b518852018-01-23 17:23:38 +010070import android.view.View;
71import android.view.View.OnLayoutChangeListener;
72import android.view.ViewGroup;
73import android.view.ViewGroup.LayoutParams;
Vishnu Nair83537a72018-07-19 21:27:48 -070074import android.view.ViewTreeObserver;
Adrian Roos5b518852018-01-23 17:23:38 +010075import android.view.WindowManager;
76import android.widget.FrameLayout;
77import android.widget.ImageView;
78
Gus Prevasab336792018-11-14 13:52:20 -050079import androidx.annotation.VisibleForTesting;
80
Adrian Roosfaa102f2018-08-02 15:56:15 +020081import com.android.internal.util.Preconditions;
Evan Lairdb0506ca2018-03-15 10:34:31 -040082import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000083import com.android.systemui.broadcast.BroadcastDispatcher;
Dave Mankoff00e8a2f2019-12-18 16:59:49 -050084import com.android.systemui.dagger.qualifiers.Main;
Adrian Roos5b518852018-01-23 17:23:38 +010085import com.android.systemui.qs.SecureSetting;
Adrian Roos5b518852018-01-23 17:23:38 +010086import com.android.systemui.tuner.TunerService;
87import com.android.systemui.tuner.TunerService.Tunable;
88
Issei Suzuki43190bd2018-08-20 17:28:41 +020089import java.util.ArrayList;
90import java.util.List;
91
Dave Mankoff4ddc25b2019-10-23 15:46:08 -040092import javax.inject.Inject;
93import javax.inject.Singleton;
94
Adrian Roos5b518852018-01-23 17:23:38 +010095/**
96 * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
97 * for antialiasing and emulation purposes.
98 */
Dave Mankoff4ddc25b2019-10-23 15:46:08 -040099@Singleton
shawnlin87af5382019-09-13 14:13:13 +0800100public class ScreenDecorations extends SystemUI implements Tunable {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200101 private static final boolean DEBUG = false;
102 private static final String TAG = "ScreenDecorations";
103
Adrian Roos5b518852018-01-23 17:23:38 +0100104 public static final String SIZE = "sysui_rounded_size";
105 public static final String PADDING = "sysui_rounded_content_padding";
Adrian Roos56d1a2c2018-03-08 23:22:19 +0100106 private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
107 SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
James O'Leary4335c702019-05-29 12:38:51 -0400108 private static final boolean VERBOSE = false;
Bill Lin3bf9ca22020-02-18 15:00:39 +0800109 private static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
Adrian Roos5b518852018-01-23 17:23:38 +0100110
Beverlye91f0d02018-05-15 14:40:47 -0400111 private DisplayManager mDisplayManager;
shawnlin014c6872020-02-26 17:51:51 +0800112 @VisibleForTesting
113 protected boolean mIsRegistered;
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000114 private final BroadcastDispatcher mBroadcastDispatcher;
115 private final Handler mMainHandler;
116 private final TunerService mTunerService;
Beverlye91f0d02018-05-15 14:40:47 -0400117 private DisplayManager.DisplayListener mDisplayListener;
Evan Laird16981452020-02-21 14:33:42 -0500118 private CameraAvailabilityListener mCameraListener;
Beverlye91f0d02018-05-15 14:40:47 -0400119
James O'Leary4335c702019-05-29 12:38:51 -0400120 @VisibleForTesting
121 protected int mRoundedDefault;
122 @VisibleForTesting
123 protected int mRoundedDefaultTop;
124 @VisibleForTesting
125 protected int mRoundedDefaultBottom;
shawnlin63dfae22020-02-16 17:15:07 +0800126 @VisibleForTesting
127 protected View[] mOverlays;
Bill Lin2f67cf72020-03-24 01:16:34 +0800128 @Nullable
129 private DisplayCutoutView[] mCutoutViews;
Adrian Roos5b518852018-01-23 17:23:38 +0100130 private float mDensity;
131 private WindowManager mWindowManager;
Beverlye91f0d02018-05-15 14:40:47 -0400132 private int mRotation;
Beverly374acde2018-06-13 10:49:26 -0400133 private SecureSetting mColorInversionSetting;
Adrian Roosfaa102f2018-08-02 15:56:15 +0200134 private Handler mHandler;
Bill Lind5e79152020-02-14 15:08:13 +0800135 private boolean mPendingRotationChange;
136 private boolean mIsRoundedCornerMultipleRadius;
Adrian Roos5b518852018-01-23 17:23:38 +0100137
Evan Laird16981452020-02-21 14:33:42 -0500138 private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
139 new CameraAvailabilityListener.CameraTransitionCallback() {
140 @Override
141 public void onApplyCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
Bill Lin2f67cf72020-03-24 01:16:34 +0800142 if (mCutoutViews == null) {
143 Log.w(TAG, "DisplayCutoutView do not initialized");
144 return;
145 }
Evan Laird16981452020-02-21 14:33:42 -0500146 // Show the extra protection around the front facing camera if necessary
147 for (DisplayCutoutView dcv : mCutoutViews) {
Bill Lin2f67cf72020-03-24 01:16:34 +0800148 // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile
149 if (dcv != null) {
150 dcv.setProtection(protectionPath, bounds);
151 dcv.setShowProtection(true);
152 }
Evan Laird16981452020-02-21 14:33:42 -0500153 }
154 }
155
156 @Override
157 public void onHideCameraProtection() {
Bill Lin2f67cf72020-03-24 01:16:34 +0800158 if (mCutoutViews == null) {
159 Log.w(TAG, "DisplayCutoutView do not initialized");
160 return;
161 }
Evan Laird16981452020-02-21 14:33:42 -0500162 // Go back to the regular anti-aliasing
163 for (DisplayCutoutView dcv : mCutoutViews) {
Bill Lin2f67cf72020-03-24 01:16:34 +0800164 // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile
165 if (dcv != null) {
166 dcv.setShowProtection(false);
167 }
Evan Laird16981452020-02-21 14:33:42 -0500168 }
169 }
170 };
171
Issei Suzuki43190bd2018-08-20 17:28:41 +0200172 /**
173 * Converts a set of {@link Rect}s into a {@link Region}
174 *
175 * @hide
176 */
177 public static Region rectsToRegion(List<Rect> rects) {
178 Region result = Region.obtain();
179 if (rects != null) {
180 for (Rect r : rects) {
181 if (r != null && !r.isEmpty()) {
182 result.op(r, Region.Op.UNION);
183 }
184 }
185 }
186 return result;
187 }
188
Dave Mankoff4ddc25b2019-10-23 15:46:08 -0400189 @Inject
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000190 public ScreenDecorations(Context context,
Dave Mankoff00e8a2f2019-12-18 16:59:49 -0500191 @Main Handler handler,
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000192 BroadcastDispatcher broadcastDispatcher,
193 TunerService tunerService) {
Dave Mankoffa5d8a392019-10-10 12:21:09 -0400194 super(context);
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000195 mMainHandler = handler;
196 mBroadcastDispatcher = broadcastDispatcher;
197 mTunerService = tunerService;
Dave Mankoffa5d8a392019-10-10 12:21:09 -0400198 }
199
Adrian Roos5b518852018-01-23 17:23:38 +0100200 @Override
201 public void start() {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200202 mHandler = startHandlerThread();
203 mHandler.post(this::startOnScreenDecorationsThread);
James O'Leary4335c702019-05-29 12:38:51 -0400204 }
205
Adrian Roosfaa102f2018-08-02 15:56:15 +0200206 @VisibleForTesting
207 Handler startHandlerThread() {
208 HandlerThread thread = new HandlerThread("ScreenDecorations");
209 thread.start();
210 return thread.getThreadHandler();
211 }
212
213 private void startOnScreenDecorationsThread() {
shawnlin63dfae22020-02-16 17:15:07 +0800214 mRotation = mContext.getDisplay().getRotation();
Adrian Roos5b518852018-01-23 17:23:38 +0100215 mWindowManager = mContext.getSystemService(WindowManager.class);
shawnlin63dfae22020-02-16 17:15:07 +0800216 mDisplayManager = mContext.getSystemService(DisplayManager.class);
Bill Lind5e79152020-02-14 15:08:13 +0800217 mIsRoundedCornerMultipleRadius = mContext.getResources().getBoolean(
218 R.bool.config_roundedCornerMultipleRadius);
Adrian Roos64c9d902018-08-20 13:43:38 +0200219 updateRoundedCornerRadii();
shawnlin63dfae22020-02-16 17:15:07 +0800220 setupDecorations();
Evan Laird16981452020-02-21 14:33:42 -0500221 setupCameraListener();
222
Beverlye91f0d02018-05-15 14:40:47 -0400223 mDisplayListener = new DisplayManager.DisplayListener() {
224 @Override
225 public void onDisplayAdded(int displayId) {
226 // do nothing
227 }
228
229 @Override
230 public void onDisplayRemoved(int displayId) {
231 // do nothing
232 }
233
234 @Override
235 public void onDisplayChanged(int displayId) {
shawnlin63dfae22020-02-16 17:15:07 +0800236 final int newRotation = mContext.getDisplay().getRotation();
237 if (mOverlays != null && mRotation != newRotation) {
Vishnu Nair83537a72018-07-19 21:27:48 -0700238 // We cannot immediately update the orientation. Otherwise
239 // WindowManager is still deferring layout until it has finished dispatching
240 // the config changes, which may cause divergence between what we draw
241 // (new orientation), and where we are placed on the screen (old orientation).
242 // Instead we wait until either:
243 // - we are trying to redraw. This because WM resized our window and told us to.
244 // - the config change has been dispatched, so WM is no longer deferring layout.
245 mPendingRotationChange = true;
Adrian Roosfaa102f2018-08-02 15:56:15 +0200246 if (DEBUG) {
247 Log.i(TAG, "Rotation changed, deferring " + newRotation + ", staying at "
248 + mRotation);
249 }
250
shawnlin63dfae22020-02-16 17:15:07 +0800251 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
252 if (mOverlays[i] != null) {
253 mOverlays[i].getViewTreeObserver().addOnPreDrawListener(
254 new RestartingPreDrawListener(mOverlays[i], i, newRotation));
255 }
256 }
Vishnu Nair83537a72018-07-19 21:27:48 -0700257 }
Beverlye91f0d02018-05-15 14:40:47 -0400258 updateOrientation();
259 }
260 };
261
Adrian Roosfaa102f2018-08-02 15:56:15 +0200262 mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
263 updateOrientation();
Adrian Roos5b518852018-01-23 17:23:38 +0100264 }
265
266 private void setupDecorations() {
shawnlin63dfae22020-02-16 17:15:07 +0800267 if (hasRoundedCorners() || shouldDrawCutout()) {
268 final DisplayCutout cutout = getCutout();
269 final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
270 int rotatedPos;
271 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
272 rotatedPos = getBoundPositionFromRotation(i, mRotation);
273 if ((bounds != null && !bounds[rotatedPos].isEmpty())
274 || shouldShowRoundedCorner(i)) {
275 createOverlay(i);
276 } else {
277 removeOverlay(i);
278 }
Adrian Roos5b518852018-01-23 17:23:38 +0100279 }
shawnlin63dfae22020-02-16 17:15:07 +0800280 } else {
281 removeAllOverlays();
282 }
Beverly374acde2018-06-13 10:49:26 -0400283
shawnlin014c6872020-02-26 17:51:51 +0800284 if (hasOverlays()) {
285 if (mIsRegistered) {
286 return;
287 }
shawnlin63dfae22020-02-16 17:15:07 +0800288 DisplayMetrics metrics = new DisplayMetrics();
289 mDisplayManager.getDisplay(DEFAULT_DISPLAY).getMetrics(metrics);
290 mDensity = metrics.density;
Adrian Roos5b518852018-01-23 17:23:38 +0100291
shawnlin63dfae22020-02-16 17:15:07 +0800292 mMainHandler.post(() -> mTunerService.addTunable(this, SIZE));
293
294 // Watch color inversion and invert the overlay as needed.
295 if (mColorInversionSetting == null) {
296 mColorInversionSetting = new SecureSetting(mContext, mHandler,
297 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
298 @Override
299 protected void handleValueChanged(int value, boolean observedChange) {
300 updateColorInversion(value);
301 }
302 };
Beverlyc5b1b562020-04-14 13:18:41 -0400303
304 mColorInversionSetting.setListening(true);
305 mColorInversionSetting.onChange(false);
shawnlin63dfae22020-02-16 17:15:07 +0800306 }
shawnlin63dfae22020-02-16 17:15:07 +0800307
308 IntentFilter filter = new IntentFilter();
309 filter.addAction(Intent.ACTION_USER_SWITCHED);
Beverlyc5b1b562020-04-14 13:18:41 -0400310 mBroadcastDispatcher.registerReceiver(mUserSwitchIntentReceiver, filter,
311 new HandlerExecutor(mHandler), UserHandle.ALL);
shawnlin63dfae22020-02-16 17:15:07 +0800312 mIsRegistered = true;
313 } else {
314 mMainHandler.post(() -> mTunerService.removeTunable(this));
315
316 if (mColorInversionSetting != null) {
317 mColorInversionSetting.setListening(false);
318 }
319
Beverlyc5b1b562020-04-14 13:18:41 -0400320 mBroadcastDispatcher.unregisterReceiver(mUserSwitchIntentReceiver);
shawnlin63dfae22020-02-16 17:15:07 +0800321 mIsRegistered = false;
322 }
323 }
324
325 @VisibleForTesting
326 DisplayCutout getCutout() {
327 return mContext.getDisplay().getCutout();
328 }
329
shawnlin014c6872020-02-26 17:51:51 +0800330 @VisibleForTesting
331 boolean hasOverlays() {
shawnlin63dfae22020-02-16 17:15:07 +0800332 if (mOverlays == null) {
333 return false;
334 }
335
336 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
337 if (mOverlays[i] != null) {
338 return true;
339 }
340 }
341 mOverlays = null;
342 return false;
343 }
344
345 private void removeAllOverlays() {
346 if (mOverlays == null) {
347 return;
348 }
349
350 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
351 if (mOverlays[i] != null) {
352 removeOverlay(i);
353 }
354 }
355 mOverlays = null;
356 }
357
358 private void removeOverlay(@BoundsPosition int pos) {
359 if (mOverlays == null || mOverlays[pos] == null) {
360 return;
361 }
362 mWindowManager.removeViewImmediate(mOverlays[pos]);
363 mOverlays[pos] = null;
364 }
365
366 private void createOverlay(@BoundsPosition int pos) {
367 if (mOverlays == null) {
368 mOverlays = new View[BOUNDS_POSITION_LENGTH];
369 }
370
371 if (mCutoutViews == null) {
372 mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH];
373 }
374
375 if (mOverlays[pos] != null) {
376 return;
377 }
378 mOverlays[pos] = LayoutInflater.from(mContext)
379 .inflate(R.layout.rounded_corners, null);
380
381 mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this);
382 ((ViewGroup) mOverlays[pos]).addView(mCutoutViews[pos]);
383
384 mOverlays[pos].setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
385 mOverlays[pos].setAlpha(0);
386 mOverlays[pos].setForceDarkAllowed(false);
387
388 updateView(pos);
389
390 mWindowManager.addView(mOverlays[pos], getWindowLayoutParams(pos));
391
392 mOverlays[pos].addOnLayoutChangeListener(new OnLayoutChangeListener() {
Adrian Roos5b518852018-01-23 17:23:38 +0100393 @Override
394 public void onLayoutChange(View v, int left, int top, int right, int bottom,
shawnlin63dfae22020-02-16 17:15:07 +0800395 int oldLeft, int oldTop, int oldRight, int oldBottom) {
396 mOverlays[pos].removeOnLayoutChangeListener(this);
397 mOverlays[pos].animate()
Adrian Roos5b518852018-01-23 17:23:38 +0100398 .alpha(1)
399 .setDuration(1000)
400 .start();
401 }
402 });
Adrian Roosfaa102f2018-08-02 15:56:15 +0200403
shawnlin63dfae22020-02-16 17:15:07 +0800404 mOverlays[pos].getViewTreeObserver().addOnPreDrawListener(
405 new ValidatingPreDrawListener(mOverlays[pos]));
406 }
407
408 private void updateView(@BoundsPosition int pos) {
409 if (mOverlays == null || mOverlays[pos] == null) {
410 return;
411 }
412
413 // update rounded corner view rotation
414 updateRoundedCornerView(pos, R.id.left);
415 updateRoundedCornerView(pos, R.id.right);
shawnlin0264a6c2020-03-24 16:45:47 +0800416 updateRoundedCornerSize(mRoundedDefault, mRoundedDefaultTop, mRoundedDefaultBottom);
shawnlin63dfae22020-02-16 17:15:07 +0800417
418 // update cutout view rotation
419 if (mCutoutViews != null && mCutoutViews[pos] != null) {
420 mCutoutViews[pos].setRotation(mRotation);
421 }
422 }
423
424 @VisibleForTesting
425 WindowManager.LayoutParams getWindowLayoutParams(@BoundsPosition int pos) {
426 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
427 getWidthLayoutParamByPos(pos),
428 getHeightLayoutParamByPos(pos),
429 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
430 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
431 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
432 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
433 | WindowManager.LayoutParams.FLAG_SLIPPERY
434 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
435 PixelFormat.TRANSLUCENT);
436 lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
437 | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
438
439 if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
440 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
441 }
442
443 lp.setTitle(getWindowTitleByPos(pos));
444 lp.gravity = getOverlayWindowGravity(pos);
445 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
446 lp.setFitInsetsTypes(0 /* types */);
447 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
448 return lp;
449 }
450
451 private int getWidthLayoutParamByPos(@BoundsPosition int pos) {
452 final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
453 return rotatedPos == BOUNDS_POSITION_TOP || rotatedPos == BOUNDS_POSITION_BOTTOM
454 ? MATCH_PARENT : WRAP_CONTENT;
455 }
456
457 private int getHeightLayoutParamByPos(@BoundsPosition int pos) {
458 final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
459 return rotatedPos == BOUNDS_POSITION_TOP || rotatedPos == BOUNDS_POSITION_BOTTOM
460 ? WRAP_CONTENT : MATCH_PARENT;
461 }
462
463 private static String getWindowTitleByPos(@BoundsPosition int pos) {
464 switch (pos) {
465 case BOUNDS_POSITION_LEFT:
466 return "ScreenDecorOverlayLeft";
467 case BOUNDS_POSITION_TOP:
468 return "ScreenDecorOverlay";
469 case BOUNDS_POSITION_RIGHT:
470 return "ScreenDecorOverlayRight";
471 case BOUNDS_POSITION_BOTTOM:
472 return "ScreenDecorOverlayBottom";
473 default:
474 throw new IllegalArgumentException("unknown bound position: " + pos);
475 }
476 }
477
478 private int getOverlayWindowGravity(@BoundsPosition int pos) {
479 final int rotated = getBoundPositionFromRotation(pos, mRotation);
480 switch (rotated) {
481 case BOUNDS_POSITION_TOP:
482 return Gravity.TOP;
483 case BOUNDS_POSITION_BOTTOM:
484 return Gravity.BOTTOM;
485 case BOUNDS_POSITION_LEFT:
486 return Gravity.LEFT;
487 case BOUNDS_POSITION_RIGHT:
488 return Gravity.RIGHT;
489 default:
490 throw new IllegalArgumentException("unknown bound position: " + pos);
491 }
492 }
493
494 private static int getBoundPositionFromRotation(@BoundsPosition int pos, int rotation) {
495 return (pos - rotation) < 0
496 ? pos - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH
497 : pos - rotation;
Adrian Roos5b518852018-01-23 17:23:38 +0100498 }
499
Evan Laird16981452020-02-21 14:33:42 -0500500 private void setupCameraListener() {
501 Resources res = mContext.getResources();
502 boolean enabled = res.getBoolean(R.bool.config_enableDisplayCutoutProtection);
503 if (enabled) {
504 mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mHandler::post);
505 mCameraListener.addTransitionCallback(mCameraTransitionCallback);
506 mCameraListener.startListening();
507 }
508 }
509
Beverlyc5b1b562020-04-14 13:18:41 -0400510 private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
Beverly374acde2018-06-13 10:49:26 -0400511 @Override
512 public void onReceive(Context context, Intent intent) {
Beverlyc5b1b562020-04-14 13:18:41 -0400513 int newUserId = ActivityManager.getCurrentUser();
514 if (DEBUG) {
515 Log.d(TAG, "UserSwitched newUserId=" + newUserId);
Beverly374acde2018-06-13 10:49:26 -0400516 }
Beverlyc5b1b562020-04-14 13:18:41 -0400517 // update color inversion setting to the new user
518 mColorInversionSetting.setUserId(newUserId);
519 updateColorInversion(mColorInversionSetting.getValue());
Beverly374acde2018-06-13 10:49:26 -0400520 }
521 };
522
523 private void updateColorInversion(int colorsInvertedValue) {
524 int tint = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
Bill Lin3bf9ca22020-02-18 15:00:39 +0800525 if (DEBUG_COLOR) {
526 tint = Color.RED;
527 }
Beverly374acde2018-06-13 10:49:26 -0400528 ColorStateList tintList = ColorStateList.valueOf(tint);
shawnlin63dfae22020-02-16 17:15:07 +0800529
530 if (mOverlays == null) {
531 return;
532 }
533 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
534 if (mOverlays[i] == null) {
535 continue;
536 }
537 final int size = ((ViewGroup) mOverlays[i]).getChildCount();
538 View child;
539 for (int j = 0; j < size; j++) {
540 child = ((ViewGroup) mOverlays[i]).getChildAt(j);
541 if (child instanceof ImageView) {
542 ((ImageView) child).setImageTintList(tintList);
543 } else if (child instanceof DisplayCutoutView) {
544 ((DisplayCutoutView) child).setColor(tint);
545 }
546 }
547 }
Beverly374acde2018-06-13 10:49:26 -0400548 }
549
Adrian Roos5b518852018-01-23 17:23:38 +0100550 @Override
551 protected void onConfigurationChanged(Configuration newConfig) {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200552 mHandler.post(() -> {
553 int oldRotation = mRotation;
554 mPendingRotationChange = false;
555 updateOrientation();
Adrian Roos64c9d902018-08-20 13:43:38 +0200556 updateRoundedCornerRadii();
Adrian Roosfaa102f2018-08-02 15:56:15 +0200557 if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
shawnlin63dfae22020-02-16 17:15:07 +0800558 setupDecorations();
559 if (mOverlays != null) {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200560 // Updating the layout params ensures that ViewRootImpl will call relayoutWindow(),
561 // which ensures that the forced seamless rotation will end, even if we updated
562 // the rotation before window manager was ready (and was still waiting for sending
563 // the updated rotation).
564 updateLayoutParams();
565 }
566 });
Beverlye91f0d02018-05-15 14:40:47 -0400567 }
568
Adrian Roosfaa102f2018-08-02 15:56:15 +0200569 private void updateOrientation() {
570 Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
571 "must call on " + mHandler.getLooper().getThread()
572 + ", but was " + Thread.currentThread());
Vishnu Nair83537a72018-07-19 21:27:48 -0700573 if (mPendingRotationChange) {
574 return;
575 }
shawnlin63dfae22020-02-16 17:15:07 +0800576 int newRotation = mContext.getDisplay().getRotation();
Beverlye91f0d02018-05-15 14:40:47 -0400577 if (newRotation != mRotation) {
578 mRotation = newRotation;
Adrian Roos5b518852018-01-23 17:23:38 +0100579
shawnlin63dfae22020-02-16 17:15:07 +0800580 if (mOverlays != null) {
Adrian Roos5b518852018-01-23 17:23:38 +0100581 updateLayoutParams();
shawnlin63dfae22020-02-16 17:15:07 +0800582 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
583 if (mOverlays[i] == null) {
584 continue;
585 }
586 updateView(i);
587 }
Adrian Roos5b518852018-01-23 17:23:38 +0100588 }
Adrian Roos5b518852018-01-23 17:23:38 +0100589 }
590 }
591
Adrian Roos64c9d902018-08-20 13:43:38 +0200592 private void updateRoundedCornerRadii() {
593 final int newRoundedDefault = mContext.getResources().getDimensionPixelSize(
Beverly4d1113d2019-01-03 14:45:10 -0500594 com.android.internal.R.dimen.rounded_corner_radius);
Adrian Roos64c9d902018-08-20 13:43:38 +0200595 final int newRoundedDefaultTop = mContext.getResources().getDimensionPixelSize(
Beverly4d1113d2019-01-03 14:45:10 -0500596 com.android.internal.R.dimen.rounded_corner_radius_top);
Adrian Roos64c9d902018-08-20 13:43:38 +0200597 final int newRoundedDefaultBottom = mContext.getResources().getDimensionPixelSize(
Beverly4d1113d2019-01-03 14:45:10 -0500598 com.android.internal.R.dimen.rounded_corner_radius_bottom);
Adrian Roos64c9d902018-08-20 13:43:38 +0200599 final boolean roundedCornersChanged = mRoundedDefault != newRoundedDefault
600 || mRoundedDefaultBottom != newRoundedDefaultBottom
601 || mRoundedDefaultTop != newRoundedDefaultTop;
602
603 if (roundedCornersChanged) {
Bill Lind5e79152020-02-14 15:08:13 +0800604 // If config_roundedCornerMultipleRadius set as true, ScreenDecorations respect the
605 // max(width, height) size of drawable/rounded.xml instead of rounded_corner_radius
606 if (mIsRoundedCornerMultipleRadius) {
607 final VectorDrawable d = (VectorDrawable) mContext.getDrawable(R.drawable.rounded);
608 mRoundedDefault = Math.max(d.getIntrinsicWidth(), d.getIntrinsicHeight());
609 mRoundedDefaultTop = mRoundedDefaultBottom = mRoundedDefault;
610 } else {
611 mRoundedDefault = newRoundedDefault;
612 mRoundedDefaultTop = newRoundedDefaultTop;
613 mRoundedDefaultBottom = newRoundedDefaultBottom;
614 }
Adrian Roos64c9d902018-08-20 13:43:38 +0200615 onTuningChanged(SIZE, null);
616 }
617 }
618
shawnlin63dfae22020-02-16 17:15:07 +0800619 private void updateRoundedCornerView(@BoundsPosition int pos, int id) {
620 final View rounded = mOverlays[pos].findViewById(id);
621 if (rounded == null) {
622 return;
Adrian Roos5b518852018-01-23 17:23:38 +0100623 }
shawnlin63dfae22020-02-16 17:15:07 +0800624 rounded.setVisibility(View.GONE);
625 if (shouldShowRoundedCorner(pos)) {
626 final int gravity = getRoundedCornerGravity(pos, id == R.id.left);
627 ((FrameLayout.LayoutParams) rounded.getLayoutParams()).gravity = gravity;
628 rounded.setRotation(getRoundedCornerRotation(gravity));
629 rounded.setVisibility(View.VISIBLE);
630 }
Adrian Roos5b518852018-01-23 17:23:38 +0100631 }
632
shawnlin63dfae22020-02-16 17:15:07 +0800633 private int getRoundedCornerGravity(@BoundsPosition int pos, boolean isStart) {
634 final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
635 switch (rotatedPos) {
636 case BOUNDS_POSITION_LEFT:
637 return isStart ? Gravity.TOP | Gravity.LEFT : Gravity.BOTTOM | Gravity.LEFT;
638 case BOUNDS_POSITION_TOP:
639 return isStart ? Gravity.TOP | Gravity.LEFT : Gravity.TOP | Gravity.RIGHT;
640 case BOUNDS_POSITION_RIGHT:
641 return isStart ? Gravity.TOP | Gravity.RIGHT : Gravity.BOTTOM | Gravity.RIGHT;
642 case BOUNDS_POSITION_BOTTOM:
643 return isStart ? Gravity.BOTTOM | Gravity.LEFT : Gravity.BOTTOM | Gravity.RIGHT;
644 default:
645 throw new IllegalArgumentException("Incorrect position: " + rotatedPos);
646 }
Adrian Roos5b518852018-01-23 17:23:38 +0100647 }
648
shawnlin63dfae22020-02-16 17:15:07 +0800649 private int getRoundedCornerRotation(int gravity) {
650 switch (gravity) {
651 case Gravity.TOP | Gravity.LEFT:
652 return 0;
653 case Gravity.TOP | Gravity.RIGHT:
654 return 90;
655 case Gravity.BOTTOM | Gravity.LEFT:
656 return 270;
657 case Gravity.BOTTOM | Gravity.RIGHT:
658 return 180;
659 default:
660 throw new IllegalArgumentException("Unsupported gravity: " + gravity);
661 }
Adrian Roos5b518852018-01-23 17:23:38 +0100662 }
663
Beverlya5f7a302018-04-25 09:19:05 -0400664 private boolean hasRoundedCorners() {
Bill Lind5e79152020-02-14 15:08:13 +0800665 return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0
666 || mIsRoundedCornerMultipleRadius;
Beverlya5f7a302018-04-25 09:19:05 -0400667 }
668
shawnlin63dfae22020-02-16 17:15:07 +0800669 private boolean shouldShowRoundedCorner(@BoundsPosition int pos) {
670 if (!hasRoundedCorners()) {
671 return false;
672 }
673
674 DisplayCutout cutout = getCutout();
675 // for cutout is null or cutout with only waterfall.
676 final boolean emptyBoundsOrWaterfall = cutout == null || cutout.isBoundsEmpty();
677 // Shows rounded corner on left and right overlays only when there is no top or bottom
678 // cutout.
679 final int rotatedTop = getBoundPositionFromRotation(BOUNDS_POSITION_TOP, mRotation);
680 final int rotatedBottom = getBoundPositionFromRotation(BOUNDS_POSITION_BOTTOM, mRotation);
681 if (emptyBoundsOrWaterfall || !cutout.getBoundingRectsAll()[rotatedTop].isEmpty()
682 || !cutout.getBoundingRectsAll()[rotatedBottom].isEmpty()) {
683 return pos == BOUNDS_POSITION_TOP || pos == BOUNDS_POSITION_BOTTOM;
684 } else {
685 return pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_RIGHT;
686 }
687 }
688
Adrian Roos5b518852018-01-23 17:23:38 +0100689 private boolean shouldDrawCutout() {
Adrian Roosc41b32b2018-04-12 10:13:48 +0200690 return shouldDrawCutout(mContext);
691 }
692
693 static boolean shouldDrawCutout(Context context) {
694 return context.getResources().getBoolean(
Adrian Roos5b518852018-01-23 17:23:38 +0100695 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
696 }
697
Adrian Roos5b518852018-01-23 17:23:38 +0100698 private void updateLayoutParams() {
shawnlin63dfae22020-02-16 17:15:07 +0800699 if (mOverlays == null) {
700 return;
701 }
702 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
703 if (mOverlays[i] == null) {
704 continue;
705 }
706 mWindowManager.updateViewLayout(mOverlays[i], getWindowLayoutParams(i));
707 }
Adrian Roos5b518852018-01-23 17:23:38 +0100708 }
709
710 @Override
711 public void onTuningChanged(String key, String newValue) {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200712 mHandler.post(() -> {
shawnlin63dfae22020-02-16 17:15:07 +0800713 if (mOverlays == null) return;
Adrian Roosfaa102f2018-08-02 15:56:15 +0200714 if (SIZE.equals(key)) {
715 int size = mRoundedDefault;
716 int sizeTop = mRoundedDefaultTop;
717 int sizeBottom = mRoundedDefaultBottom;
718 if (newValue != null) {
719 try {
720 size = (int) (Integer.parseInt(newValue) * mDensity);
721 } catch (Exception e) {
722 }
Beverlya5f7a302018-04-25 09:19:05 -0400723 }
shawnlin0264a6c2020-03-24 16:45:47 +0800724 updateRoundedCornerSize(size, sizeTop, sizeBottom);
Adrian Roosfaa102f2018-08-02 15:56:15 +0200725 }
726 });
Adrian Roos5b518852018-01-23 17:23:38 +0100727 }
728
shawnlin0264a6c2020-03-24 16:45:47 +0800729 private void updateRoundedCornerSize(int sizeDefault, int sizeTop, int sizeBottom) {
730 if (mOverlays == null) {
731 return;
732 }
733 if (sizeTop == 0) {
734 sizeTop = sizeDefault;
735 }
736 if (sizeBottom == 0) {
737 sizeBottom = sizeDefault;
738 }
739
740 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
741 if (mOverlays[i] == null) {
742 continue;
743 }
744 if (i == BOUNDS_POSITION_LEFT || i == BOUNDS_POSITION_RIGHT) {
745 if (mRotation == ROTATION_270) {
746 setSize(mOverlays[i].findViewById(R.id.left), sizeBottom);
747 setSize(mOverlays[i].findViewById(R.id.right), sizeTop);
748 } else {
749 setSize(mOverlays[i].findViewById(R.id.left), sizeTop);
750 setSize(mOverlays[i].findViewById(R.id.right), sizeBottom);
751 }
752 } else if (i == BOUNDS_POSITION_TOP) {
753 setSize(mOverlays[i].findViewById(R.id.left), sizeTop);
754 setSize(mOverlays[i].findViewById(R.id.right), sizeTop);
755 } else if (i == BOUNDS_POSITION_BOTTOM) {
756 setSize(mOverlays[i].findViewById(R.id.left), sizeBottom);
757 setSize(mOverlays[i].findViewById(R.id.right), sizeBottom);
758 }
759 }
760 }
761
762 @VisibleForTesting
763 protected void setSize(View view, int pixelSize) {
Adrian Roos5b518852018-01-23 17:23:38 +0100764 LayoutParams params = view.getLayoutParams();
765 params.width = pixelSize;
766 params.height = pixelSize;
767 view.setLayoutParams(params);
768 }
769
Evan Lairdb0506ca2018-03-15 10:34:31 -0400770 public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener,
771 RegionInterceptableView {
Adrian Roos5b518852018-01-23 17:23:38 +0100772
Lucas Dupin2b05ccc2020-03-04 14:15:52 -0800773 private static final float HIDDEN_CAMERA_PROTECTION_SCALE = 0.5f;
774
Adrian Roos5b518852018-01-23 17:23:38 +0100775 private final DisplayInfo mInfo = new DisplayInfo();
776 private final Paint mPaint = new Paint();
Issei Suzuki43190bd2018-08-20 17:28:41 +0200777 private final List<Rect> mBounds = new ArrayList();
Adrian Roos5b518852018-01-23 17:23:38 +0100778 private final Rect mBoundingRect = new Rect();
779 private final Path mBoundingPath = new Path();
Evan Laird377838b2020-03-06 16:55:46 -0500780 // Don't initialize these yet because they may never exist
Lucas Dupin789046212020-03-10 14:13:02 -0700781 private RectF mProtectionRect;
782 private RectF mProtectionRectOrig;
Evan Laird16981452020-02-21 14:33:42 -0500783 private Path mProtectionPath;
Evan Laird377838b2020-03-06 16:55:46 -0500784 private Path mProtectionPathOrig;
Evan Laird16981452020-02-21 14:33:42 -0500785 private Rect mTotalBounds = new Rect();
786 // Whether or not to show the cutout protection path
787 private boolean mShowProtection = false;
788
Adrian Roos5b518852018-01-23 17:23:38 +0100789 private final int[] mLocation = new int[2];
Vishnu Nair83537a72018-07-19 21:27:48 -0700790 private final ScreenDecorations mDecorations;
Adrian Roos8b29a842018-05-31 14:14:13 +0200791 private int mColor = Color.BLACK;
Adrian Roos9acf8132018-05-31 19:54:53 +0200792 private int mRotation;
shawnlin63dfae22020-02-16 17:15:07 +0800793 private int mInitialPosition;
794 private int mPosition;
Lucas Dupin2b05ccc2020-03-04 14:15:52 -0800795 private float mCameraProtectionProgress = HIDDEN_CAMERA_PROTECTION_SCALE;
796 private ValueAnimator mCameraProtectionAnimator;
Adrian Roos5b518852018-01-23 17:23:38 +0100797
shawnlin63dfae22020-02-16 17:15:07 +0800798 public DisplayCutoutView(Context context, @BoundsPosition int pos,
799 ScreenDecorations decorations) {
Adrian Roos5b518852018-01-23 17:23:38 +0100800 super(context);
shawnlin63dfae22020-02-16 17:15:07 +0800801 mInitialPosition = pos;
Vishnu Nair83537a72018-07-19 21:27:48 -0700802 mDecorations = decorations;
Adrian Roos5b518852018-01-23 17:23:38 +0100803 setId(R.id.display_cutout);
Adrian Roosfaa102f2018-08-02 15:56:15 +0200804 if (DEBUG) {
805 getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG,
shawnlin63dfae22020-02-16 17:15:07 +0800806 getWindowTitleByPos(pos) + " drawn in rot " + mRotation));
Adrian Roosfaa102f2018-08-02 15:56:15 +0200807 }
Adrian Roos5b518852018-01-23 17:23:38 +0100808 }
809
Adrian Roos8b29a842018-05-31 14:14:13 +0200810 public void setColor(int color) {
811 mColor = color;
812 invalidate();
813 }
814
Adrian Roos5b518852018-01-23 17:23:38 +0100815 @Override
816 protected void onAttachedToWindow() {
817 super.onAttachedToWindow();
818 mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
819 getHandler());
820 update();
821 }
822
823 @Override
824 protected void onDetachedFromWindow() {
825 super.onDetachedFromWindow();
826 mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
827 }
828
829 @Override
830 protected void onDraw(Canvas canvas) {
831 super.onDraw(canvas);
832 getLocationOnScreen(mLocation);
833 canvas.translate(-mLocation[0], -mLocation[1]);
Evan Laird16981452020-02-21 14:33:42 -0500834
Lucas Dupin2b05ccc2020-03-04 14:15:52 -0800835 if (!mBoundingPath.isEmpty()) {
Adrian Roos8b29a842018-05-31 14:14:13 +0200836 mPaint.setColor(mColor);
Adrian Roos5b518852018-01-23 17:23:38 +0100837 mPaint.setStyle(Paint.Style.FILL);
Adrian Roos51072a82018-04-10 15:17:08 -0700838 mPaint.setAntiAlias(true);
Adrian Roos5b518852018-01-23 17:23:38 +0100839 canvas.drawPath(mBoundingPath, mPaint);
840 }
Lucas Dupin2b05ccc2020-03-04 14:15:52 -0800841 if (mCameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE
842 && !mProtectionRect.isEmpty()) {
843 canvas.scale(mCameraProtectionProgress, mCameraProtectionProgress,
844 mProtectionRect.centerX(), mProtectionRect.centerY());
845 canvas.drawPath(mProtectionPath, mPaint);
846 }
Adrian Roos5b518852018-01-23 17:23:38 +0100847 }
848
849 @Override
850 public void onDisplayAdded(int displayId) {
851 }
852
853 @Override
854 public void onDisplayRemoved(int displayId) {
855 }
856
857 @Override
858 public void onDisplayChanged(int displayId) {
859 if (displayId == getDisplay().getDisplayId()) {
860 update();
861 }
862 }
863
Adrian Roos9acf8132018-05-31 19:54:53 +0200864 public void setRotation(int rotation) {
865 mRotation = rotation;
866 update();
867 }
868
Evan Laird16981452020-02-21 14:33:42 -0500869 void setProtection(Path protectionPath, Rect pathBounds) {
Evan Laird377838b2020-03-06 16:55:46 -0500870 if (mProtectionPathOrig == null) {
871 mProtectionPathOrig = new Path();
872 mProtectionPath = new Path();
873 }
874 mProtectionPathOrig.set(protectionPath);
Lucas Dupin789046212020-03-10 14:13:02 -0700875 if (mProtectionRectOrig == null) {
876 mProtectionRectOrig = new RectF();
877 mProtectionRect = new RectF();
878 }
879 mProtectionRectOrig.set(pathBounds);
Evan Laird16981452020-02-21 14:33:42 -0500880 }
881
882 void setShowProtection(boolean shouldShow) {
883 if (mShowProtection == shouldShow) {
884 return;
885 }
886
887 mShowProtection = shouldShow;
888 updateBoundingPath();
Lucas Dupin2b05ccc2020-03-04 14:15:52 -0800889 // Delay the relayout until the end of the animation when hiding the cutout,
890 // otherwise we'd clip it.
891 if (mShowProtection) {
892 requestLayout();
893 }
894 if (mCameraProtectionAnimator != null) {
895 mCameraProtectionAnimator.cancel();
896 }
897 mCameraProtectionAnimator = ValueAnimator.ofFloat(mCameraProtectionProgress,
898 mShowProtection ? 1.0f : HIDDEN_CAMERA_PROTECTION_SCALE).setDuration(750);
899 mCameraProtectionAnimator.setInterpolator(Interpolators.DECELERATE_QUINT);
900 mCameraProtectionAnimator.addUpdateListener(animation -> {
901 mCameraProtectionProgress = (float) animation.getAnimatedValue();
902 invalidate();
903 });
904 mCameraProtectionAnimator.addListener(new AnimatorListenerAdapter() {
905 @Override
906 public void onAnimationEnd(Animator animation) {
907 mCameraProtectionAnimator = null;
908 if (!mShowProtection) {
909 requestLayout();
910 }
911 }
912 });
913 mCameraProtectionAnimator.start();
Evan Laird16981452020-02-21 14:33:42 -0500914 }
915
Adrian Roos5b518852018-01-23 17:23:38 +0100916 private void update() {
Vishnu Nair83537a72018-07-19 21:27:48 -0700917 if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) {
Adrian Roos9acf8132018-05-31 19:54:53 +0200918 return;
919 }
shawnlin63dfae22020-02-16 17:15:07 +0800920 mPosition = getBoundPositionFromRotation(mInitialPosition, mRotation);
Adrian Roos5b518852018-01-23 17:23:38 +0100921 requestLayout();
922 getDisplay().getDisplayInfo(mInfo);
Issei Suzuki43190bd2018-08-20 17:28:41 +0200923 mBounds.clear();
Adrian Roos5b518852018-01-23 17:23:38 +0100924 mBoundingRect.setEmpty();
925 mBoundingPath.reset();
926 int newVisible;
Adrian Roosc41b32b2018-04-12 10:13:48 +0200927 if (shouldDrawCutout(getContext()) && hasCutout()) {
Issei Suzuki43190bd2018-08-20 17:28:41 +0200928 mBounds.addAll(mInfo.displayCutout.getBoundingRects());
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100929 localBounds(mBoundingRect);
Adrian Roos0098aee2019-06-03 16:47:02 +0200930 updateGravity();
Adrian Roos51072a82018-04-10 15:17:08 -0700931 updateBoundingPath();
Adrian Roos1b028282018-03-14 14:43:03 +0100932 invalidate();
Adrian Roos5b518852018-01-23 17:23:38 +0100933 newVisible = VISIBLE;
934 } else {
935 newVisible = GONE;
936 }
937 if (newVisible != getVisibility()) {
938 setVisibility(newVisible);
Adrian Roos5b518852018-01-23 17:23:38 +0100939 }
940 }
941
Adrian Roos51072a82018-04-10 15:17:08 -0700942 private void updateBoundingPath() {
943 int lw = mInfo.logicalWidth;
944 int lh = mInfo.logicalHeight;
945
946 boolean flipped = mInfo.rotation == ROTATION_90 || mInfo.rotation == ROTATION_270;
947
948 int dw = flipped ? lh : lw;
949 int dh = flipped ? lw : lh;
950
Adrian Roosa99f5d62018-04-16 16:03:04 +0200951 mBoundingPath.set(DisplayCutout.pathFromResources(getResources(), dw, dh));
Adrian Roos51072a82018-04-10 15:17:08 -0700952 Matrix m = new Matrix();
953 transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
954 mBoundingPath.transform(m);
Evan Laird377838b2020-03-06 16:55:46 -0500955 if (mProtectionPathOrig != null) {
956 // Reset the protection path so we don't aggregate rotations
957 mProtectionPath.set(mProtectionPathOrig);
Evan Laird16981452020-02-21 14:33:42 -0500958 mProtectionPath.transform(m);
Lucas Dupin789046212020-03-10 14:13:02 -0700959 m.mapRect(mProtectionRect, mProtectionRectOrig);
Evan Laird16981452020-02-21 14:33:42 -0500960 }
Adrian Roos51072a82018-04-10 15:17:08 -0700961 }
962
963 private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation,
964 @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
965 switch (rotation) {
966 case ROTATION_0:
967 out.reset();
968 break;
969 case ROTATION_90:
970 out.setRotate(270);
971 out.postTranslate(0, physicalWidth);
972 break;
973 case ROTATION_180:
974 out.setRotate(180);
975 out.postTranslate(physicalWidth, physicalHeight);
976 break;
977 case ROTATION_270:
978 out.setRotate(90);
979 out.postTranslate(physicalHeight, 0);
980 break;
981 default:
982 throw new IllegalArgumentException("Unknown rotation: " + rotation);
983 }
984 }
985
Adrian Roos0098aee2019-06-03 16:47:02 +0200986 private void updateGravity() {
987 LayoutParams lp = getLayoutParams();
988 if (lp instanceof FrameLayout.LayoutParams) {
989 FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) lp;
990 int newGravity = getGravity(mInfo.displayCutout);
991 if (flp.gravity != newGravity) {
992 flp.gravity = newGravity;
993 setLayoutParams(flp);
994 }
995 }
996 }
997
Adrian Roos5b518852018-01-23 17:23:38 +0100998 private boolean hasCutout() {
Adrian Roos24264212018-02-19 16:26:15 +0100999 final DisplayCutout displayCutout = mInfo.displayCutout;
1000 if (displayCutout == null) {
Adrian Roos5b518852018-01-23 17:23:38 +01001001 return false;
1002 }
shawnlin63dfae22020-02-16 17:15:07 +08001003
1004 if (mPosition == BOUNDS_POSITION_LEFT) {
1005 return !displayCutout.getBoundingRectLeft().isEmpty();
1006 } else if (mPosition == BOUNDS_POSITION_TOP) {
1007 return !displayCutout.getBoundingRectTop().isEmpty();
1008 } else if (mPosition == BOUNDS_POSITION_BOTTOM) {
1009 return !displayCutout.getBoundingRectBottom().isEmpty();
1010 } else if (mPosition == BOUNDS_POSITION_RIGHT) {
1011 return !displayCutout.getBoundingRectRight().isEmpty();
Adrian Roos5b518852018-01-23 17:23:38 +01001012 }
shawnlin63dfae22020-02-16 17:15:07 +08001013 return false;
Adrian Roos5b518852018-01-23 17:23:38 +01001014 }
1015
1016 @Override
1017 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001018 if (mBounds.isEmpty()) {
Adrian Roos5b518852018-01-23 17:23:38 +01001019 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1020 return;
1021 }
Evan Laird16981452020-02-21 14:33:42 -05001022
1023 if (mShowProtection) {
1024 // Make sure that our measured height encompases the protection
1025 mTotalBounds.union(mBoundingRect);
Lucas Dupin789046212020-03-10 14:13:02 -07001026 mTotalBounds.union((int) mProtectionRect.left, (int) mProtectionRect.top,
1027 (int) mProtectionRect.right, (int) mProtectionRect.bottom);
Evan Laird16981452020-02-21 14:33:42 -05001028 setMeasuredDimension(
1029 resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
1030 resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0));
1031 } else {
1032 setMeasuredDimension(
1033 resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
1034 resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
1035 }
Adrian Roos5b518852018-01-23 17:23:38 +01001036 }
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001037
Jorim Jaggi71aaa402018-06-06 17:22:56 +02001038 public static void boundsFromDirection(DisplayCutout displayCutout, int gravity,
1039 Rect out) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001040 switch (gravity) {
1041 case Gravity.TOP:
Issei Suzuki43190bd2018-08-20 17:28:41 +02001042 out.set(displayCutout.getBoundingRectTop());
Evan Laird6703f422018-10-11 13:24:05 -04001043 break;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001044 case Gravity.LEFT:
Issei Suzuki43190bd2018-08-20 17:28:41 +02001045 out.set(displayCutout.getBoundingRectLeft());
Evan Laird6703f422018-10-11 13:24:05 -04001046 break;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001047 case Gravity.BOTTOM:
Issei Suzuki43190bd2018-08-20 17:28:41 +02001048 out.set(displayCutout.getBoundingRectBottom());
Evan Laird6703f422018-10-11 13:24:05 -04001049 break;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001050 case Gravity.RIGHT:
Issei Suzuki43190bd2018-08-20 17:28:41 +02001051 out.set(displayCutout.getBoundingRectRight());
Evan Laird6703f422018-10-11 13:24:05 -04001052 break;
1053 default:
1054 out.setEmpty();
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001055 }
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001056 }
1057
1058 private void localBounds(Rect out) {
Adrian Roos0098aee2019-06-03 16:47:02 +02001059 DisplayCutout displayCutout = mInfo.displayCutout;
1060 boundsFromDirection(displayCutout, getGravity(displayCutout), out);
1061 }
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001062
Adrian Roos0098aee2019-06-03 16:47:02 +02001063 private int getGravity(DisplayCutout displayCutout) {
shawnlin63dfae22020-02-16 17:15:07 +08001064 if (mPosition == BOUNDS_POSITION_LEFT) {
shawnlinbd6d0ad2020-01-29 18:38:49 +08001065 if (!displayCutout.getBoundingRectLeft().isEmpty()) {
Adrian Roos0098aee2019-06-03 16:47:02 +02001066 return Gravity.LEFT;
shawnlin63dfae22020-02-16 17:15:07 +08001067 }
1068 } else if (mPosition == BOUNDS_POSITION_TOP) {
1069 if (!displayCutout.getBoundingRectTop().isEmpty()) {
Adrian Roos0098aee2019-06-03 16:47:02 +02001070 return Gravity.TOP;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001071 }
shawnlin63dfae22020-02-16 17:15:07 +08001072 } else if (mPosition == BOUNDS_POSITION_BOTTOM) {
1073 if (!displayCutout.getBoundingRectBottom().isEmpty()) {
1074 return Gravity.BOTTOM;
1075 }
1076 } else if (mPosition == BOUNDS_POSITION_RIGHT) {
shawnlinbd6d0ad2020-01-29 18:38:49 +08001077 if (!displayCutout.getBoundingRectRight().isEmpty()) {
Adrian Roos0098aee2019-06-03 16:47:02 +02001078 return Gravity.RIGHT;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001079 }
1080 }
Adrian Roos0098aee2019-06-03 16:47:02 +02001081 return Gravity.NO_GRAVITY;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001082 }
Evan Lairdb0506ca2018-03-15 10:34:31 -04001083
1084 @Override
1085 public boolean shouldInterceptTouch() {
1086 return mInfo.displayCutout != null && getVisibility() == VISIBLE;
1087 }
1088
1089 @Override
1090 public Region getInterceptRegion() {
1091 if (mInfo.displayCutout == null) {
1092 return null;
1093 }
1094
Adrian Roos134e1cb2018-05-16 17:04:29 +02001095 View rootView = getRootView();
Issei Suzuki43190bd2018-08-20 17:28:41 +02001096 Region cutoutBounds = rectsToRegion(
1097 mInfo.displayCutout.getBoundingRects());
Adrian Roos134e1cb2018-05-16 17:04:29 +02001098
1099 // Transform to window's coordinate space
1100 rootView.getLocationOnScreen(mLocation);
1101 cutoutBounds.translate(-mLocation[0], -mLocation[1]);
1102
1103 // Intersect with window's frame
1104 cutoutBounds.op(rootView.getLeft(), rootView.getTop(), rootView.getRight(),
1105 rootView.getBottom(), Region.Op.INTERSECT);
1106
1107 return cutoutBounds;
Evan Lairdb0506ca2018-03-15 10:34:31 -04001108 }
Adrian Roos5b518852018-01-23 17:23:38 +01001109 }
Beverlye91f0d02018-05-15 14:40:47 -04001110
Vishnu Nair83537a72018-07-19 21:27:48 -07001111 /**
1112 * A pre-draw listener, that cancels the draw and restarts the traversal with the updated
1113 * window attributes.
1114 */
1115 private class RestartingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
1116
1117 private final View mView;
Adrian Roosfaa102f2018-08-02 15:56:15 +02001118 private final int mTargetRotation;
shawnlin63dfae22020-02-16 17:15:07 +08001119 private final int mPosition;
Vishnu Nair83537a72018-07-19 21:27:48 -07001120
shawnlin63dfae22020-02-16 17:15:07 +08001121 private RestartingPreDrawListener(View view, @BoundsPosition int position,
1122 int targetRotation) {
Adrian Roosfaa102f2018-08-02 15:56:15 +02001123 mView = view;
1124 mTargetRotation = targetRotation;
shawnlin63dfae22020-02-16 17:15:07 +08001125 mPosition = position;
Adrian Roosfaa102f2018-08-02 15:56:15 +02001126 }
1127
1128 @Override
1129 public boolean onPreDraw() {
1130 mView.getViewTreeObserver().removeOnPreDrawListener(this);
1131
1132 if (mTargetRotation == mRotation) {
1133 if (DEBUG) {
shawnlin63dfae22020-02-16 17:15:07 +08001134 Log.i(TAG, getWindowTitleByPos(mPosition) + " already in target rot "
Adrian Roosfaa102f2018-08-02 15:56:15 +02001135 + mTargetRotation + ", allow draw without restarting it");
1136 }
1137 return true;
1138 }
1139
1140 mPendingRotationChange = false;
1141 // This changes the window attributes - we need to restart the traversal for them to
1142 // take effect.
1143 updateOrientation();
1144 if (DEBUG) {
shawnlin63dfae22020-02-16 17:15:07 +08001145 Log.i(TAG, getWindowTitleByPos(mPosition)
Adrian Roosfaa102f2018-08-02 15:56:15 +02001146 + " restarting listener fired, restarting draw for rot " + mRotation);
1147 }
1148 mView.invalidate();
1149 return false;
1150 }
1151 }
1152
1153 /**
1154 * A pre-draw listener, that validates that the rotation we draw in matches the displays
1155 * rotation before continuing the draw.
1156 *
1157 * This is to prevent a race condition, where we have not received the display changed event
1158 * yet, and would thus draw in an old orientation.
1159 */
1160 private class ValidatingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
1161
1162 private final View mView;
1163
1164 public ValidatingPreDrawListener(View view) {
Adrian Roos61f557a2018-08-02 15:56:15 +02001165 mView = view;
Adrian Roos61f557a2018-08-02 15:56:15 +02001166 }
1167
1168 @Override
1169 public boolean onPreDraw() {
shawnlin63dfae22020-02-16 17:15:07 +08001170 final int displayRotation = mContext.getDisplay().getRotation();
Adrian Roosfaa102f2018-08-02 15:56:15 +02001171 if (displayRotation != mRotation && !mPendingRotationChange) {
1172 if (DEBUG) {
1173 Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
1174 + displayRotation + ". Restarting draw");
1175 }
1176 mView.invalidate();
1177 return false;
1178 }
1179 return true;
Adrian Roos61f557a2018-08-02 15:56:15 +02001180 }
1181 }
Adrian Roos5b518852018-01-23 17:23:38 +01001182}