blob: c0f03a6f4ef86c9f15bca16ee73e5ec74c9b2b36 [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;
Beverly374acde2018-06-13 10:49:26 -040029import android.app.ActivityManager;
Adrian Roos5b518852018-01-23 17:23:38 +010030import android.app.Fragment;
Beverly374acde2018-06-13 10:49:26 -040031import android.content.BroadcastReceiver;
Adrian Roos5b518852018-01-23 17:23:38 +010032import android.content.Context;
Beverly374acde2018-06-13 10:49:26 -040033import android.content.Intent;
34import android.content.IntentFilter;
Adrian Roos5b518852018-01-23 17:23:38 +010035import android.content.res.ColorStateList;
36import android.content.res.Configuration;
37import android.graphics.Canvas;
38import android.graphics.Color;
Adrian Roos51072a82018-04-10 15:17:08 -070039import android.graphics.Matrix;
Adrian Roos5b518852018-01-23 17:23:38 +010040import android.graphics.Paint;
41import android.graphics.Path;
42import android.graphics.PixelFormat;
43import android.graphics.Rect;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010044import android.graphics.Region;
Adrian Roos5b518852018-01-23 17:23:38 +010045import android.hardware.display.DisplayManager;
Adrian Roosfaa102f2018-08-02 15:56:15 +020046import android.os.Handler;
47import android.os.HandlerThread;
Adrian Roos56d1a2c2018-03-08 23:22:19 +010048import android.os.SystemProperties;
Adrian Roos5b518852018-01-23 17:23:38 +010049import android.provider.Settings.Secure;
Adrian Roos5b518852018-01-23 17:23:38 +010050import android.util.DisplayMetrics;
Adrian Roosfaa102f2018-08-02 15:56:15 +020051import android.util.Log;
Adrian Roos5b518852018-01-23 17:23:38 +010052import android.view.DisplayCutout;
53import android.view.DisplayInfo;
54import android.view.Gravity;
55import android.view.LayoutInflater;
Adrian Roos51072a82018-04-10 15:17:08 -070056import android.view.Surface;
Adrian Roos5b518852018-01-23 17:23:38 +010057import android.view.View;
58import android.view.View.OnLayoutChangeListener;
59import android.view.ViewGroup;
60import android.view.ViewGroup.LayoutParams;
Vishnu Nair83537a72018-07-19 21:27:48 -070061import android.view.ViewTreeObserver;
Adrian Roos5b518852018-01-23 17:23:38 +010062import android.view.WindowManager;
63import android.widget.FrameLayout;
64import android.widget.ImageView;
65
Gus Prevasab336792018-11-14 13:52:20 -050066import androidx.annotation.VisibleForTesting;
67
Adrian Roosfaa102f2018-08-02 15:56:15 +020068import com.android.internal.util.Preconditions;
Evan Lairdb0506ca2018-03-15 10:34:31 -040069import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
Adrian Roos5b518852018-01-23 17:23:38 +010070import com.android.systemui.fragments.FragmentHostManager;
71import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
72import com.android.systemui.plugins.qs.QS;
73import com.android.systemui.qs.SecureSetting;
74import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
75import com.android.systemui.statusbar.phone.StatusBar;
76import com.android.systemui.tuner.TunablePadding;
77import com.android.systemui.tuner.TunerService;
78import com.android.systemui.tuner.TunerService.Tunable;
Beverlye91f0d02018-05-15 14:40:47 -040079import com.android.systemui.util.leak.RotationUtils;
Adrian Roos5b518852018-01-23 17:23:38 +010080
Issei Suzuki43190bd2018-08-20 17:28:41 +020081import java.util.ArrayList;
82import java.util.List;
83
Adrian Roos5b518852018-01-23 17:23:38 +010084/**
85 * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
86 * for antialiasing and emulation purposes.
87 */
88public class ScreenDecorations extends SystemUI implements Tunable {
Adrian Roosfaa102f2018-08-02 15:56:15 +020089 private static final boolean DEBUG = false;
90 private static final String TAG = "ScreenDecorations";
91
Adrian Roos5b518852018-01-23 17:23:38 +010092 public static final String SIZE = "sysui_rounded_size";
93 public static final String PADDING = "sysui_rounded_content_padding";
Adrian Roos56d1a2c2018-03-08 23:22:19 +010094 private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
95 SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
Adrian Roos5b518852018-01-23 17:23:38 +010096
Beverlye91f0d02018-05-15 14:40:47 -040097 private DisplayManager mDisplayManager;
98 private DisplayManager.DisplayListener mDisplayListener;
99
Adrian Roos64c9d902018-08-20 13:43:38 +0200100 @VisibleForTesting protected int mRoundedDefault;
101 @VisibleForTesting protected int mRoundedDefaultTop;
102 @VisibleForTesting protected int mRoundedDefaultBottom;
Adrian Roos5b518852018-01-23 17:23:38 +0100103 private View mOverlay;
104 private View mBottomOverlay;
105 private float mDensity;
106 private WindowManager mWindowManager;
Beverlye91f0d02018-05-15 14:40:47 -0400107 private int mRotation;
Adrian Roos9acf8132018-05-31 19:54:53 +0200108 private DisplayCutoutView mCutoutTop;
109 private DisplayCutoutView mCutoutBottom;
Beverly374acde2018-06-13 10:49:26 -0400110 private SecureSetting mColorInversionSetting;
Vishnu Nair83537a72018-07-19 21:27:48 -0700111 private boolean mPendingRotationChange;
Adrian Roosfaa102f2018-08-02 15:56:15 +0200112 private Handler mHandler;
Adrian Roos5b518852018-01-23 17:23:38 +0100113
Issei Suzuki43190bd2018-08-20 17:28:41 +0200114 /**
115 * Converts a set of {@link Rect}s into a {@link Region}
116 *
117 * @hide
118 */
119 public static Region rectsToRegion(List<Rect> rects) {
120 Region result = Region.obtain();
121 if (rects != null) {
122 for (Rect r : rects) {
123 if (r != null && !r.isEmpty()) {
124 result.op(r, Region.Op.UNION);
125 }
126 }
127 }
128 return result;
129 }
130
Adrian Roos5b518852018-01-23 17:23:38 +0100131 @Override
132 public void start() {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200133 mHandler = startHandlerThread();
134 mHandler.post(this::startOnScreenDecorationsThread);
135 setupStatusBarPaddingIfNeeded();
136 }
137
138 @VisibleForTesting
139 Handler startHandlerThread() {
140 HandlerThread thread = new HandlerThread("ScreenDecorations");
141 thread.start();
142 return thread.getThreadHandler();
143 }
144
145 private void startOnScreenDecorationsThread() {
146 mRotation = RotationUtils.getExactRotation(mContext);
Adrian Roos5b518852018-01-23 17:23:38 +0100147 mWindowManager = mContext.getSystemService(WindowManager.class);
Adrian Roos64c9d902018-08-20 13:43:38 +0200148 updateRoundedCornerRadii();
Beverlya5f7a302018-04-25 09:19:05 -0400149 if (hasRoundedCorners() || shouldDrawCutout()) {
Adrian Roos5b518852018-01-23 17:23:38 +0100150 setupDecorations();
151 }
Beverlya5f7a302018-04-25 09:19:05 -0400152
Beverlye91f0d02018-05-15 14:40:47 -0400153 mDisplayListener = new DisplayManager.DisplayListener() {
154 @Override
155 public void onDisplayAdded(int displayId) {
156 // do nothing
157 }
158
159 @Override
160 public void onDisplayRemoved(int displayId) {
161 // do nothing
162 }
163
164 @Override
165 public void onDisplayChanged(int displayId) {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200166 final int newRotation = RotationUtils.getExactRotation(mContext);
167 if (mOverlay != null && mBottomOverlay != null && mRotation != newRotation) {
Vishnu Nair83537a72018-07-19 21:27:48 -0700168 // We cannot immediately update the orientation. Otherwise
169 // WindowManager is still deferring layout until it has finished dispatching
170 // the config changes, which may cause divergence between what we draw
171 // (new orientation), and where we are placed on the screen (old orientation).
172 // Instead we wait until either:
173 // - we are trying to redraw. This because WM resized our window and told us to.
174 // - the config change has been dispatched, so WM is no longer deferring layout.
175 mPendingRotationChange = true;
Adrian Roosfaa102f2018-08-02 15:56:15 +0200176 if (DEBUG) {
177 Log.i(TAG, "Rotation changed, deferring " + newRotation + ", staying at "
178 + mRotation);
179 }
180
Vishnu Nair83537a72018-07-19 21:27:48 -0700181 mOverlay.getViewTreeObserver().addOnPreDrawListener(
Adrian Roosfaa102f2018-08-02 15:56:15 +0200182 new RestartingPreDrawListener(mOverlay, newRotation));
Vishnu Nair83537a72018-07-19 21:27:48 -0700183 mBottomOverlay.getViewTreeObserver().addOnPreDrawListener(
Adrian Roosfaa102f2018-08-02 15:56:15 +0200184 new RestartingPreDrawListener(mBottomOverlay, newRotation));
Vishnu Nair83537a72018-07-19 21:27:48 -0700185 }
Beverlye91f0d02018-05-15 14:40:47 -0400186 updateOrientation();
187 }
188 };
189
Beverlye91f0d02018-05-15 14:40:47 -0400190 mDisplayManager = (DisplayManager) mContext.getSystemService(
191 Context.DISPLAY_SERVICE);
Adrian Roosfaa102f2018-08-02 15:56:15 +0200192 mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
193 updateOrientation();
Adrian Roos5b518852018-01-23 17:23:38 +0100194 }
195
196 private void setupDecorations() {
197 mOverlay = LayoutInflater.from(mContext)
198 .inflate(R.layout.rounded_corners, null);
Adrian Roos9acf8132018-05-31 19:54:53 +0200199 mCutoutTop = new DisplayCutoutView(mContext, true,
Vishnu Nair83537a72018-07-19 21:27:48 -0700200 this::updateWindowVisibilities, this);
Adrian Roos9acf8132018-05-31 19:54:53 +0200201 ((ViewGroup)mOverlay).addView(mCutoutTop);
Adrian Roos5b518852018-01-23 17:23:38 +0100202 mBottomOverlay = LayoutInflater.from(mContext)
203 .inflate(R.layout.rounded_corners, null);
Adrian Roos9acf8132018-05-31 19:54:53 +0200204 mCutoutBottom = new DisplayCutoutView(mContext, false,
Vishnu Nair83537a72018-07-19 21:27:48 -0700205 this::updateWindowVisibilities, this);
Adrian Roos9acf8132018-05-31 19:54:53 +0200206 ((ViewGroup)mBottomOverlay).addView(mCutoutBottom);
Adrian Roos5b518852018-01-23 17:23:38 +0100207
208 mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
209 mOverlay.setAlpha(0);
John Recka2d20b42018-10-01 12:21:55 -0700210 mOverlay.setForceDarkAllowed(false);
Adrian Roos5b518852018-01-23 17:23:38 +0100211
212 mBottomOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
213 mBottomOverlay.setAlpha(0);
John Recka2d20b42018-10-01 12:21:55 -0700214 mBottomOverlay.setForceDarkAllowed(false);
Adrian Roos5b518852018-01-23 17:23:38 +0100215
216 updateViews();
217
218 mWindowManager.addView(mOverlay, getWindowLayoutParams());
219 mWindowManager.addView(mBottomOverlay, getBottomLayoutParams());
220
221 DisplayMetrics metrics = new DisplayMetrics();
222 mWindowManager.getDefaultDisplay().getMetrics(metrics);
223 mDensity = metrics.density;
224
Adrian Roosfaa102f2018-08-02 15:56:15 +0200225 Dependency.get(Dependency.MAIN_HANDLER).post(
226 () -> Dependency.get(TunerService.class).addTunable(this, SIZE));
Adrian Roos5b518852018-01-23 17:23:38 +0100227
228 // Watch color inversion and invert the overlay as needed.
Adrian Roosfaa102f2018-08-02 15:56:15 +0200229 mColorInversionSetting = new SecureSetting(mContext, mHandler,
Adrian Roos5b518852018-01-23 17:23:38 +0100230 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
231 @Override
232 protected void handleValueChanged(int value, boolean observedChange) {
Beverly374acde2018-06-13 10:49:26 -0400233 updateColorInversion(value);
Adrian Roos5b518852018-01-23 17:23:38 +0100234 }
235 };
Beverly374acde2018-06-13 10:49:26 -0400236 mColorInversionSetting.setListening(true);
237 mColorInversionSetting.onChange(false);
238
239 IntentFilter filter = new IntentFilter();
240 filter.addAction(Intent.ACTION_USER_SWITCHED);
Adrian Roosfaa102f2018-08-02 15:56:15 +0200241 mContext.registerReceiver(mIntentReceiver, filter, null /* permission */, mHandler);
Adrian Roos5b518852018-01-23 17:23:38 +0100242
243 mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
244 @Override
245 public void onLayoutChange(View v, int left, int top, int right, int bottom,
246 int oldLeft,
247 int oldTop, int oldRight, int oldBottom) {
248 mOverlay.removeOnLayoutChangeListener(this);
249 mOverlay.animate()
250 .alpha(1)
251 .setDuration(1000)
252 .start();
253 mBottomOverlay.animate()
254 .alpha(1)
255 .setDuration(1000)
256 .start();
257 }
258 });
Adrian Roosfaa102f2018-08-02 15:56:15 +0200259
260 mOverlay.getViewTreeObserver().addOnPreDrawListener(
261 new ValidatingPreDrawListener(mOverlay));
262 mBottomOverlay.getViewTreeObserver().addOnPreDrawListener(
263 new ValidatingPreDrawListener(mBottomOverlay));
Adrian Roos5b518852018-01-23 17:23:38 +0100264 }
265
Beverly374acde2018-06-13 10:49:26 -0400266 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
267 @Override
268 public void onReceive(Context context, Intent intent) {
269 String action = intent.getAction();
270 if (action.equals(Intent.ACTION_USER_SWITCHED)) {
271 int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
272 ActivityManager.getCurrentUser());
273 // update color inversion setting to the new user
274 mColorInversionSetting.setUserId(newUserId);
275 updateColorInversion(mColorInversionSetting.getValue());
276 }
277 }
278 };
279
280 private void updateColorInversion(int colorsInvertedValue) {
281 int tint = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
282 ColorStateList tintList = ColorStateList.valueOf(tint);
283 ((ImageView) mOverlay.findViewById(R.id.left)).setImageTintList(tintList);
284 ((ImageView) mOverlay.findViewById(R.id.right)).setImageTintList(tintList);
285 ((ImageView) mBottomOverlay.findViewById(R.id.left)).setImageTintList(tintList);
286 ((ImageView) mBottomOverlay.findViewById(R.id.right)).setImageTintList(tintList);
287 mCutoutTop.setColor(tint);
288 mCutoutBottom.setColor(tint);
289 }
290
Adrian Roos5b518852018-01-23 17:23:38 +0100291 @Override
292 protected void onConfigurationChanged(Configuration newConfig) {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200293 mHandler.post(() -> {
294 int oldRotation = mRotation;
295 mPendingRotationChange = false;
296 updateOrientation();
Adrian Roos64c9d902018-08-20 13:43:38 +0200297 updateRoundedCornerRadii();
Adrian Roosfaa102f2018-08-02 15:56:15 +0200298 if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
299 if (shouldDrawCutout() && mOverlay == null) {
300 setupDecorations();
301 }
302 if (mOverlay != null) {
303 // Updating the layout params ensures that ViewRootImpl will call relayoutWindow(),
304 // which ensures that the forced seamless rotation will end, even if we updated
305 // the rotation before window manager was ready (and was still waiting for sending
306 // the updated rotation).
307 updateLayoutParams();
308 }
309 });
Beverlye91f0d02018-05-15 14:40:47 -0400310 }
311
Adrian Roosfaa102f2018-08-02 15:56:15 +0200312 private void updateOrientation() {
313 Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
314 "must call on " + mHandler.getLooper().getThread()
315 + ", but was " + Thread.currentThread());
Vishnu Nair83537a72018-07-19 21:27:48 -0700316 if (mPendingRotationChange) {
317 return;
318 }
Beverlye91f0d02018-05-15 14:40:47 -0400319 int newRotation = RotationUtils.getExactRotation(mContext);
320 if (newRotation != mRotation) {
321 mRotation = newRotation;
Adrian Roos5b518852018-01-23 17:23:38 +0100322
323 if (mOverlay != null) {
324 updateLayoutParams();
325 updateViews();
326 }
Adrian Roos5b518852018-01-23 17:23:38 +0100327 }
328 }
329
Adrian Roos64c9d902018-08-20 13:43:38 +0200330 private void updateRoundedCornerRadii() {
331 final int newRoundedDefault = mContext.getResources().getDimensionPixelSize(
Beverly4d1113d2019-01-03 14:45:10 -0500332 com.android.internal.R.dimen.rounded_corner_radius);
Adrian Roos64c9d902018-08-20 13:43:38 +0200333 final int newRoundedDefaultTop = mContext.getResources().getDimensionPixelSize(
Beverly4d1113d2019-01-03 14:45:10 -0500334 com.android.internal.R.dimen.rounded_corner_radius_top);
Adrian Roos64c9d902018-08-20 13:43:38 +0200335 final int newRoundedDefaultBottom = mContext.getResources().getDimensionPixelSize(
Beverly4d1113d2019-01-03 14:45:10 -0500336 com.android.internal.R.dimen.rounded_corner_radius_bottom);
Adrian Roos64c9d902018-08-20 13:43:38 +0200337
338 final boolean roundedCornersChanged = mRoundedDefault != newRoundedDefault
339 || mRoundedDefaultBottom != newRoundedDefaultBottom
340 || mRoundedDefaultTop != newRoundedDefaultTop;
341
342 if (roundedCornersChanged) {
343 mRoundedDefault = newRoundedDefault;
344 mRoundedDefaultTop = newRoundedDefaultTop;
345 mRoundedDefaultBottom = newRoundedDefaultBottom;
346 onTuningChanged(SIZE, null);
347 }
348 }
349
Adrian Roos5b518852018-01-23 17:23:38 +0100350 private void updateViews() {
351 View topLeft = mOverlay.findViewById(R.id.left);
352 View topRight = mOverlay.findViewById(R.id.right);
353 View bottomLeft = mBottomOverlay.findViewById(R.id.left);
354 View bottomRight = mBottomOverlay.findViewById(R.id.right);
Beverlye91f0d02018-05-15 14:40:47 -0400355
356 if (mRotation == RotationUtils.ROTATION_NONE) {
357 updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
358 updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
359 updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
360 updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
361 } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
362 updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
363 updateView(topRight, Gravity.BOTTOM | Gravity.LEFT, 270);
364 updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);;
365 updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
366 } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
367 updateView(topLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
368 updateView(topRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
369 updateView(bottomLeft, Gravity.TOP | Gravity.LEFT, 0);
370 updateView(bottomRight, Gravity.TOP | Gravity.RIGHT, 90);
371 } else if (mRotation == RotationUtils.ROTATION_SEASCAPE) {
372 updateView(topLeft, Gravity.BOTTOM | Gravity.RIGHT, 180);
373 updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
374 updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
375 updateView(bottomRight, Gravity.TOP | Gravity.LEFT, 0);
Adrian Roos5b518852018-01-23 17:23:38 +0100376 }
Adrian Roos5b518852018-01-23 17:23:38 +0100377
Adrian Roos9acf8132018-05-31 19:54:53 +0200378 mCutoutTop.setRotation(mRotation);
379 mCutoutBottom.setRotation(mRotation);
380
Adrian Roos5b518852018-01-23 17:23:38 +0100381 updateWindowVisibilities();
382 }
383
384 private void updateView(View v, int gravity, int rotation) {
385 ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity;
386 v.setRotation(rotation);
387 }
388
389 private void updateWindowVisibilities() {
390 updateWindowVisibility(mOverlay);
391 updateWindowVisibility(mBottomOverlay);
392 }
393
394 private void updateWindowVisibility(View overlay) {
395 boolean visibleForCutout = shouldDrawCutout()
396 && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE;
Beverlya5f7a302018-04-25 09:19:05 -0400397 boolean visibleForRoundedCorners = hasRoundedCorners();
Adrian Roos5b518852018-01-23 17:23:38 +0100398 overlay.setVisibility(visibleForCutout || visibleForRoundedCorners
399 ? View.VISIBLE : View.GONE);
400 }
401
Beverlya5f7a302018-04-25 09:19:05 -0400402 private boolean hasRoundedCorners() {
403 return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0;
404 }
405
Adrian Roos5b518852018-01-23 17:23:38 +0100406 private boolean shouldDrawCutout() {
Adrian Roosc41b32b2018-04-12 10:13:48 +0200407 return shouldDrawCutout(mContext);
408 }
409
410 static boolean shouldDrawCutout(Context context) {
411 return context.getResources().getBoolean(
Adrian Roos5b518852018-01-23 17:23:38 +0100412 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
413 }
414
Adrian Roosfaa102f2018-08-02 15:56:15 +0200415
416 private void setupStatusBarPaddingIfNeeded() {
417 // TODO: This should be moved to a more appropriate place, as it is not related to the
418 // screen decorations overlay.
419 int padding = mContext.getResources().getDimensionPixelSize(
420 R.dimen.rounded_corner_content_padding);
421 if (padding != 0) {
422 setupStatusBarPadding(padding);
423 }
424
425 }
426
427 private void setupStatusBarPadding(int padding) {
Adrian Roos5b518852018-01-23 17:23:38 +0100428 // Add some padding to all the content near the edge of the screen.
429 StatusBar sb = getComponent(StatusBar.class);
430 View statusBar = (sb != null ? sb.getStatusBarWindow() : null);
431 if (statusBar != null) {
432 TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING,
433 padding, FLAG_END);
434
435 FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar);
436 fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
437 new TunablePaddingTagListener(padding, R.id.status_bar));
438 fragmentHostManager.addTagListener(QS.TAG,
439 new TunablePaddingTagListener(padding, R.id.header));
440 }
441 }
442
443 @VisibleForTesting
444 WindowManager.LayoutParams getWindowLayoutParams() {
445 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
446 ViewGroup.LayoutParams.MATCH_PARENT,
447 LayoutParams.WRAP_CONTENT,
448 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
Evan Lairdb0506ca2018-03-15 10:34:31 -0400449 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
Adrian Roos5b518852018-01-23 17:23:38 +0100450 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
451 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
452 | WindowManager.LayoutParams.FLAG_SLIPPERY
453 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
454 PixelFormat.TRANSLUCENT);
Robert Carr772e8bc2018-03-14 11:51:23 -0700455 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
456 | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
457
Adrian Roos56d1a2c2018-03-08 23:22:19 +0100458 if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
459 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
460 }
Robert Carr772e8bc2018-03-14 11:51:23 -0700461
Adrian Roos5b518852018-01-23 17:23:38 +0100462 lp.setTitle("ScreenDecorOverlay");
Beverlye91f0d02018-05-15 14:40:47 -0400463 if (mRotation == RotationUtils.ROTATION_SEASCAPE
464 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
465 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
466 } else {
467 lp.gravity = Gravity.TOP | Gravity.LEFT;
468 }
Adrian Roos5b518852018-01-23 17:23:38 +0100469 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
Beverlye91f0d02018-05-15 14:40:47 -0400470 if (isLandscape(mRotation)) {
Adrian Roos5b518852018-01-23 17:23:38 +0100471 lp.width = WRAP_CONTENT;
472 lp.height = MATCH_PARENT;
473 }
Peiyong Lin75045382019-03-04 19:22:33 -0800474 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
Adrian Roos5b518852018-01-23 17:23:38 +0100475 return lp;
476 }
477
478 private WindowManager.LayoutParams getBottomLayoutParams() {
479 WindowManager.LayoutParams lp = getWindowLayoutParams();
480 lp.setTitle("ScreenDecorOverlayBottom");
Beverlye91f0d02018-05-15 14:40:47 -0400481 if (mRotation == RotationUtils.ROTATION_SEASCAPE
482 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
483 lp.gravity = Gravity.TOP | Gravity.LEFT;
484 } else {
485 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
486 }
Adrian Roos5b518852018-01-23 17:23:38 +0100487 return lp;
488 }
489
490 private void updateLayoutParams() {
491 mWindowManager.updateViewLayout(mOverlay, getWindowLayoutParams());
492 mWindowManager.updateViewLayout(mBottomOverlay, getBottomLayoutParams());
493 }
494
495 @Override
496 public void onTuningChanged(String key, String newValue) {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200497 mHandler.post(() -> {
498 if (mOverlay == null) return;
499 if (SIZE.equals(key)) {
500 int size = mRoundedDefault;
501 int sizeTop = mRoundedDefaultTop;
502 int sizeBottom = mRoundedDefaultBottom;
503 if (newValue != null) {
504 try {
505 size = (int) (Integer.parseInt(newValue) * mDensity);
506 } catch (Exception e) {
507 }
Beverlya5f7a302018-04-25 09:19:05 -0400508 }
Adrian Roos72940642018-08-09 15:23:56 +0000509
Adrian Roosfaa102f2018-08-02 15:56:15 +0200510 if (sizeTop == 0) {
511 sizeTop = size;
512 }
513 if (sizeBottom == 0) {
514 sizeBottom = size;
515 }
Adrian Roos72940642018-08-09 15:23:56 +0000516
Adrian Roosfaa102f2018-08-02 15:56:15 +0200517 setSize(mOverlay.findViewById(R.id.left), sizeTop);
518 setSize(mOverlay.findViewById(R.id.right), sizeTop);
519 setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom);
520 setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom);
521 }
522 });
Adrian Roos5b518852018-01-23 17:23:38 +0100523 }
524
525 private void setSize(View view, int pixelSize) {
526 LayoutParams params = view.getLayoutParams();
527 params.width = pixelSize;
528 params.height = pixelSize;
529 view.setLayoutParams(params);
530 }
531
532 @VisibleForTesting
533 static class TunablePaddingTagListener implements FragmentListener {
534
535 private final int mPadding;
536 private final int mId;
537 private TunablePadding mTunablePadding;
538
539 public TunablePaddingTagListener(int padding, int id) {
540 mPadding = padding;
541 mId = id;
542 }
543
544 @Override
545 public void onFragmentViewCreated(String tag, Fragment fragment) {
546 if (mTunablePadding != null) {
547 mTunablePadding.destroy();
548 }
549 View view = fragment.getView();
550 if (mId != 0) {
551 view = view.findViewById(mId);
552 }
553 mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding,
554 FLAG_START | FLAG_END);
555 }
556 }
557
Evan Lairdb0506ca2018-03-15 10:34:31 -0400558 public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener,
559 RegionInterceptableView {
Adrian Roos5b518852018-01-23 17:23:38 +0100560
561 private final DisplayInfo mInfo = new DisplayInfo();
562 private final Paint mPaint = new Paint();
Issei Suzuki43190bd2018-08-20 17:28:41 +0200563 private final List<Rect> mBounds = new ArrayList();
Adrian Roos5b518852018-01-23 17:23:38 +0100564 private final Rect mBoundingRect = new Rect();
565 private final Path mBoundingPath = new Path();
566 private final int[] mLocation = new int[2];
Adrian Roos9acf8132018-05-31 19:54:53 +0200567 private final boolean mInitialStart;
Adrian Roos5b518852018-01-23 17:23:38 +0100568 private final Runnable mVisibilityChangedListener;
Vishnu Nair83537a72018-07-19 21:27:48 -0700569 private final ScreenDecorations mDecorations;
Adrian Roos8b29a842018-05-31 14:14:13 +0200570 private int mColor = Color.BLACK;
Adrian Roos9acf8132018-05-31 19:54:53 +0200571 private boolean mStart;
572 private int mRotation;
Adrian Roos5b518852018-01-23 17:23:38 +0100573
574 public DisplayCutoutView(Context context, boolean start,
Vishnu Nair83537a72018-07-19 21:27:48 -0700575 Runnable visibilityChangedListener, ScreenDecorations decorations) {
Adrian Roos5b518852018-01-23 17:23:38 +0100576 super(context);
Adrian Roos9acf8132018-05-31 19:54:53 +0200577 mInitialStart = start;
Adrian Roos5b518852018-01-23 17:23:38 +0100578 mVisibilityChangedListener = visibilityChangedListener;
Vishnu Nair83537a72018-07-19 21:27:48 -0700579 mDecorations = decorations;
Adrian Roos5b518852018-01-23 17:23:38 +0100580 setId(R.id.display_cutout);
Adrian Roosfaa102f2018-08-02 15:56:15 +0200581 if (DEBUG) {
582 getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG,
583 (mInitialStart ? "OverlayTop" : "OverlayBottom")
584 + " drawn in rot " + mRotation));
585 }
Adrian Roos5b518852018-01-23 17:23:38 +0100586 }
587
Adrian Roos8b29a842018-05-31 14:14:13 +0200588 public void setColor(int color) {
589 mColor = color;
590 invalidate();
591 }
592
Adrian Roos5b518852018-01-23 17:23:38 +0100593 @Override
594 protected void onAttachedToWindow() {
595 super.onAttachedToWindow();
596 mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
597 getHandler());
598 update();
599 }
600
601 @Override
602 protected void onDetachedFromWindow() {
603 super.onDetachedFromWindow();
604 mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
605 }
606
607 @Override
608 protected void onDraw(Canvas canvas) {
609 super.onDraw(canvas);
610 getLocationOnScreen(mLocation);
611 canvas.translate(-mLocation[0], -mLocation[1]);
612 if (!mBoundingPath.isEmpty()) {
Adrian Roos8b29a842018-05-31 14:14:13 +0200613 mPaint.setColor(mColor);
Adrian Roos5b518852018-01-23 17:23:38 +0100614 mPaint.setStyle(Paint.Style.FILL);
Adrian Roos51072a82018-04-10 15:17:08 -0700615 mPaint.setAntiAlias(true);
Adrian Roos5b518852018-01-23 17:23:38 +0100616 canvas.drawPath(mBoundingPath, mPaint);
617 }
618 }
619
620 @Override
621 public void onDisplayAdded(int displayId) {
622 }
623
624 @Override
625 public void onDisplayRemoved(int displayId) {
626 }
627
628 @Override
629 public void onDisplayChanged(int displayId) {
630 if (displayId == getDisplay().getDisplayId()) {
631 update();
632 }
633 }
634
Adrian Roos9acf8132018-05-31 19:54:53 +0200635 public void setRotation(int rotation) {
636 mRotation = rotation;
637 update();
638 }
639
640 private boolean isStart() {
641 final boolean flipped = (mRotation == RotationUtils.ROTATION_SEASCAPE
642 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN);
643 return flipped ? !mInitialStart : mInitialStart;
644 }
645
Adrian Roos5b518852018-01-23 17:23:38 +0100646 private void update() {
Vishnu Nair83537a72018-07-19 21:27:48 -0700647 if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) {
Adrian Roos9acf8132018-05-31 19:54:53 +0200648 return;
649 }
Vishnu Nair83537a72018-07-19 21:27:48 -0700650 mStart = isStart();
Adrian Roos5b518852018-01-23 17:23:38 +0100651 requestLayout();
652 getDisplay().getDisplayInfo(mInfo);
Issei Suzuki43190bd2018-08-20 17:28:41 +0200653 mBounds.clear();
Adrian Roos5b518852018-01-23 17:23:38 +0100654 mBoundingRect.setEmpty();
655 mBoundingPath.reset();
656 int newVisible;
Adrian Roosc41b32b2018-04-12 10:13:48 +0200657 if (shouldDrawCutout(getContext()) && hasCutout()) {
Issei Suzuki43190bd2018-08-20 17:28:41 +0200658 mBounds.addAll(mInfo.displayCutout.getBoundingRects());
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100659 localBounds(mBoundingRect);
Adrian Roos51072a82018-04-10 15:17:08 -0700660 updateBoundingPath();
Adrian Roos1b028282018-03-14 14:43:03 +0100661 invalidate();
Adrian Roos5b518852018-01-23 17:23:38 +0100662 newVisible = VISIBLE;
663 } else {
664 newVisible = GONE;
665 }
666 if (newVisible != getVisibility()) {
667 setVisibility(newVisible);
668 mVisibilityChangedListener.run();
669 }
670 }
671
Adrian Roos51072a82018-04-10 15:17:08 -0700672 private void updateBoundingPath() {
673 int lw = mInfo.logicalWidth;
674 int lh = mInfo.logicalHeight;
675
676 boolean flipped = mInfo.rotation == ROTATION_90 || mInfo.rotation == ROTATION_270;
677
678 int dw = flipped ? lh : lw;
679 int dh = flipped ? lw : lh;
680
Adrian Roosa99f5d62018-04-16 16:03:04 +0200681 mBoundingPath.set(DisplayCutout.pathFromResources(getResources(), dw, dh));
Adrian Roos51072a82018-04-10 15:17:08 -0700682 Matrix m = new Matrix();
683 transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
684 mBoundingPath.transform(m);
685 }
686
687 private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation,
688 @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
689 switch (rotation) {
690 case ROTATION_0:
691 out.reset();
692 break;
693 case ROTATION_90:
694 out.setRotate(270);
695 out.postTranslate(0, physicalWidth);
696 break;
697 case ROTATION_180:
698 out.setRotate(180);
699 out.postTranslate(physicalWidth, physicalHeight);
700 break;
701 case ROTATION_270:
702 out.setRotate(90);
703 out.postTranslate(physicalHeight, 0);
704 break;
705 default:
706 throw new IllegalArgumentException("Unknown rotation: " + rotation);
707 }
708 }
709
Adrian Roos5b518852018-01-23 17:23:38 +0100710 private boolean hasCutout() {
Adrian Roos24264212018-02-19 16:26:15 +0100711 final DisplayCutout displayCutout = mInfo.displayCutout;
712 if (displayCutout == null) {
Adrian Roos5b518852018-01-23 17:23:38 +0100713 return false;
714 }
Adrian Roos5b518852018-01-23 17:23:38 +0100715 if (mStart) {
716 return displayCutout.getSafeInsetLeft() > 0
717 || displayCutout.getSafeInsetTop() > 0;
718 } else {
719 return displayCutout.getSafeInsetRight() > 0
720 || displayCutout.getSafeInsetBottom() > 0;
721 }
722 }
723
724 @Override
725 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100726 if (mBounds.isEmpty()) {
Adrian Roos5b518852018-01-23 17:23:38 +0100727 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
728 return;
729 }
730 setMeasuredDimension(
731 resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
732 resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
733 }
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100734
Jorim Jaggi71aaa402018-06-06 17:22:56 +0200735 public static void boundsFromDirection(DisplayCutout displayCutout, int gravity,
736 Rect out) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100737 switch (gravity) {
738 case Gravity.TOP:
Issei Suzuki43190bd2018-08-20 17:28:41 +0200739 out.set(displayCutout.getBoundingRectTop());
Evan Laird6703f422018-10-11 13:24:05 -0400740 break;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100741 case Gravity.LEFT:
Issei Suzuki43190bd2018-08-20 17:28:41 +0200742 out.set(displayCutout.getBoundingRectLeft());
Evan Laird6703f422018-10-11 13:24:05 -0400743 break;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100744 case Gravity.BOTTOM:
Issei Suzuki43190bd2018-08-20 17:28:41 +0200745 out.set(displayCutout.getBoundingRectBottom());
Evan Laird6703f422018-10-11 13:24:05 -0400746 break;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100747 case Gravity.RIGHT:
Issei Suzuki43190bd2018-08-20 17:28:41 +0200748 out.set(displayCutout.getBoundingRectRight());
Evan Laird6703f422018-10-11 13:24:05 -0400749 break;
750 default:
751 out.setEmpty();
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100752 }
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100753 }
754
755 private void localBounds(Rect out) {
756 final DisplayCutout displayCutout = mInfo.displayCutout;
757
758 if (mStart) {
759 if (displayCutout.getSafeInsetLeft() > 0) {
760 boundsFromDirection(displayCutout, Gravity.LEFT, out);
761 } else if (displayCutout.getSafeInsetTop() > 0) {
762 boundsFromDirection(displayCutout, Gravity.TOP, out);
763 }
764 } else {
765 if (displayCutout.getSafeInsetRight() > 0) {
766 boundsFromDirection(displayCutout, Gravity.RIGHT, out);
767 } else if (displayCutout.getSafeInsetBottom() > 0) {
768 boundsFromDirection(displayCutout, Gravity.BOTTOM, out);
769 }
770 }
771 }
Evan Lairdb0506ca2018-03-15 10:34:31 -0400772
773 @Override
774 public boolean shouldInterceptTouch() {
775 return mInfo.displayCutout != null && getVisibility() == VISIBLE;
776 }
777
778 @Override
779 public Region getInterceptRegion() {
780 if (mInfo.displayCutout == null) {
781 return null;
782 }
783
Adrian Roos134e1cb2018-05-16 17:04:29 +0200784 View rootView = getRootView();
Issei Suzuki43190bd2018-08-20 17:28:41 +0200785 Region cutoutBounds = rectsToRegion(
786 mInfo.displayCutout.getBoundingRects());
Adrian Roos134e1cb2018-05-16 17:04:29 +0200787
788 // Transform to window's coordinate space
789 rootView.getLocationOnScreen(mLocation);
790 cutoutBounds.translate(-mLocation[0], -mLocation[1]);
791
792 // Intersect with window's frame
793 cutoutBounds.op(rootView.getLeft(), rootView.getTop(), rootView.getRight(),
794 rootView.getBottom(), Region.Op.INTERSECT);
795
796 return cutoutBounds;
Evan Lairdb0506ca2018-03-15 10:34:31 -0400797 }
Adrian Roos5b518852018-01-23 17:23:38 +0100798 }
Beverlye91f0d02018-05-15 14:40:47 -0400799
800 private boolean isLandscape(int rotation) {
801 return rotation == RotationUtils.ROTATION_LANDSCAPE || rotation ==
802 RotationUtils.ROTATION_SEASCAPE;
803 }
Vishnu Nair83537a72018-07-19 21:27:48 -0700804
805 /**
806 * A pre-draw listener, that cancels the draw and restarts the traversal with the updated
807 * window attributes.
808 */
809 private class RestartingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
810
811 private final View mView;
Adrian Roosfaa102f2018-08-02 15:56:15 +0200812 private final int mTargetRotation;
Vishnu Nair83537a72018-07-19 21:27:48 -0700813
Adrian Roosfaa102f2018-08-02 15:56:15 +0200814 private RestartingPreDrawListener(View view, int targetRotation) {
815 mView = view;
816 mTargetRotation = targetRotation;
817 }
818
819 @Override
820 public boolean onPreDraw() {
821 mView.getViewTreeObserver().removeOnPreDrawListener(this);
822
823 if (mTargetRotation == mRotation) {
824 if (DEBUG) {
825 Log.i(TAG, (mView == mOverlay ? "OverlayTop" : "OverlayBottom")
826 + " already in target rot "
827 + mTargetRotation + ", allow draw without restarting it");
828 }
829 return true;
830 }
831
832 mPendingRotationChange = false;
833 // This changes the window attributes - we need to restart the traversal for them to
834 // take effect.
835 updateOrientation();
836 if (DEBUG) {
837 Log.i(TAG, (mView == mOverlay ? "OverlayTop" : "OverlayBottom")
838 + " restarting listener fired, restarting draw for rot " + mRotation);
839 }
840 mView.invalidate();
841 return false;
842 }
843 }
844
845 /**
846 * A pre-draw listener, that validates that the rotation we draw in matches the displays
847 * rotation before continuing the draw.
848 *
849 * This is to prevent a race condition, where we have not received the display changed event
850 * yet, and would thus draw in an old orientation.
851 */
852 private class ValidatingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
853
854 private final View mView;
855
856 public ValidatingPreDrawListener(View view) {
Adrian Roos61f557a2018-08-02 15:56:15 +0200857 mView = view;
Adrian Roos61f557a2018-08-02 15:56:15 +0200858 }
859
860 @Override
861 public boolean onPreDraw() {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200862 final int displayRotation = RotationUtils.getExactRotation(mContext);
863 if (displayRotation != mRotation && !mPendingRotationChange) {
864 if (DEBUG) {
865 Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
866 + displayRotation + ". Restarting draw");
867 }
868 mView.invalidate();
869 return false;
870 }
871 return true;
Adrian Roos61f557a2018-08-02 15:56:15 +0200872 }
873 }
Adrian Roos5b518852018-01-23 17:23:38 +0100874}