blob: f38b4f259c88ca6775ea781794a2112d1f755ac6 [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
Matt Casey9fedd812019-06-05 17:14:09 -040028import android.animation.Animator;
29import android.animation.AnimatorSet;
30import android.animation.ObjectAnimator;
Adrian Roos51072a82018-04-10 15:17:08 -070031import android.annotation.Dimension;
Beverly374acde2018-06-13 10:49:26 -040032import android.app.ActivityManager;
Adrian Roos5b518852018-01-23 17:23:38 +010033import android.app.Fragment;
Beverly374acde2018-06-13 10:49:26 -040034import android.content.BroadcastReceiver;
Adrian Roos5b518852018-01-23 17:23:38 +010035import android.content.Context;
Beverly374acde2018-06-13 10:49:26 -040036import android.content.Intent;
37import android.content.IntentFilter;
Adrian Roos5b518852018-01-23 17:23:38 +010038import android.content.res.ColorStateList;
39import android.content.res.Configuration;
40import android.graphics.Canvas;
41import android.graphics.Color;
Adrian Roos51072a82018-04-10 15:17:08 -070042import android.graphics.Matrix;
Adrian Roos5b518852018-01-23 17:23:38 +010043import android.graphics.Paint;
44import android.graphics.Path;
45import android.graphics.PixelFormat;
46import android.graphics.Rect;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010047import android.graphics.Region;
Adrian Roos5b518852018-01-23 17:23:38 +010048import android.hardware.display.DisplayManager;
Adrian Roosfaa102f2018-08-02 15:56:15 +020049import android.os.Handler;
50import android.os.HandlerThread;
Adrian Roos56d1a2c2018-03-08 23:22:19 +010051import android.os.SystemProperties;
Adrian Roos5b518852018-01-23 17:23:38 +010052import android.provider.Settings.Secure;
Adrian Roos5b518852018-01-23 17:23:38 +010053import android.util.DisplayMetrics;
Adrian Roosfaa102f2018-08-02 15:56:15 +020054import android.util.Log;
Matt Casey9fedd812019-06-05 17:14:09 -040055import android.util.MathUtils;
Adrian Roos5b518852018-01-23 17:23:38 +010056import android.view.DisplayCutout;
57import android.view.DisplayInfo;
58import android.view.Gravity;
59import android.view.LayoutInflater;
Adrian Roos51072a82018-04-10 15:17:08 -070060import android.view.Surface;
Adrian Roos5b518852018-01-23 17:23:38 +010061import android.view.View;
62import android.view.View.OnLayoutChangeListener;
63import android.view.ViewGroup;
64import android.view.ViewGroup.LayoutParams;
Vishnu Nair83537a72018-07-19 21:27:48 -070065import android.view.ViewTreeObserver;
Adrian Roos5b518852018-01-23 17:23:38 +010066import android.view.WindowManager;
Govinda Wasserman4f706c22019-06-12 14:41:36 -040067import android.view.animation.AccelerateInterpolator;
Matt Casey9fedd812019-06-05 17:14:09 -040068import android.view.animation.Interpolator;
James O'Leary4335c702019-05-29 12:38:51 -040069import android.view.animation.PathInterpolator;
Adrian Roos5b518852018-01-23 17:23:38 +010070import android.widget.FrameLayout;
71import android.widget.ImageView;
72
Gus Prevasab336792018-11-14 13:52:20 -050073import androidx.annotation.VisibleForTesting;
74
Adrian Roosfaa102f2018-08-02 15:56:15 +020075import com.android.internal.util.Preconditions;
Evan Lairdb0506ca2018-03-15 10:34:31 -040076import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
Adrian Roos5b518852018-01-23 17:23:38 +010077import com.android.systemui.fragments.FragmentHostManager;
78import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
79import com.android.systemui.plugins.qs.QS;
80import com.android.systemui.qs.SecureSetting;
Matt Casey97193c22019-06-04 18:55:51 -040081import com.android.systemui.shared.system.QuickStepContract;
Adrian Roos5b518852018-01-23 17:23:38 +010082import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
James O'Leary4335c702019-05-29 12:38:51 -040083import com.android.systemui.statusbar.phone.NavigationBarTransitions;
Matt Casey97193c22019-06-04 18:55:51 -040084import com.android.systemui.statusbar.phone.NavigationModeController;
Adrian Roos5b518852018-01-23 17:23:38 +010085import com.android.systemui.statusbar.phone.StatusBar;
86import com.android.systemui.tuner.TunablePadding;
87import com.android.systemui.tuner.TunerService;
88import com.android.systemui.tuner.TunerService.Tunable;
Beverlye91f0d02018-05-15 14:40:47 -040089import com.android.systemui.util.leak.RotationUtils;
Adrian Roos5b518852018-01-23 17:23:38 +010090
Issei Suzuki43190bd2018-08-20 17:28:41 +020091import java.util.ArrayList;
92import java.util.List;
93
Adrian Roos5b518852018-01-23 17:23:38 +010094/**
95 * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
96 * for antialiasing and emulation purposes.
97 */
James O'Leary4335c702019-05-29 12:38:51 -040098public class ScreenDecorations extends SystemUI implements Tunable,
99 NavigationBarTransitions.DarkIntensityListener {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200100 private static final boolean DEBUG = false;
101 private static final String TAG = "ScreenDecorations";
102
Adrian Roos5b518852018-01-23 17:23:38 +0100103 public static final String SIZE = "sysui_rounded_size";
104 public static final String PADDING = "sysui_rounded_content_padding";
Adrian Roos56d1a2c2018-03-08 23:22:19 +0100105 private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
106 SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
James O'Leary4335c702019-05-29 12:38:51 -0400107 private static final boolean VERBOSE = false;
Adrian Roos5b518852018-01-23 17:23:38 +0100108
Beverlye91f0d02018-05-15 14:40:47 -0400109 private DisplayManager mDisplayManager;
110 private DisplayManager.DisplayListener mDisplayListener;
111
James O'Leary4335c702019-05-29 12:38:51 -0400112 @VisibleForTesting
113 protected int mRoundedDefault;
114 @VisibleForTesting
115 protected int mRoundedDefaultTop;
116 @VisibleForTesting
117 protected int mRoundedDefaultBottom;
Adrian Roos5b518852018-01-23 17:23:38 +0100118 private View mOverlay;
119 private View mBottomOverlay;
120 private float mDensity;
121 private WindowManager mWindowManager;
Beverlye91f0d02018-05-15 14:40:47 -0400122 private int mRotation;
Mark Renouf0255e162019-05-21 12:54:58 -0400123 private boolean mAssistHintVisible;
Adrian Roos9acf8132018-05-31 19:54:53 +0200124 private DisplayCutoutView mCutoutTop;
125 private DisplayCutoutView mCutoutBottom;
Beverly374acde2018-06-13 10:49:26 -0400126 private SecureSetting mColorInversionSetting;
Vishnu Nair83537a72018-07-19 21:27:48 -0700127 private boolean mPendingRotationChange;
Adrian Roosfaa102f2018-08-02 15:56:15 +0200128 private Handler mHandler;
James O'Leary4335c702019-05-29 12:38:51 -0400129 private boolean mAssistHintBlocked = false;
130 private boolean mIsReceivingNavBarColor = false;
Matt Casey97193c22019-06-04 18:55:51 -0400131 private boolean mInGesturalMode;
Adrian Roos5b518852018-01-23 17:23:38 +0100132
Issei Suzuki43190bd2018-08-20 17:28:41 +0200133 /**
134 * Converts a set of {@link Rect}s into a {@link Region}
135 *
136 * @hide
137 */
138 public static Region rectsToRegion(List<Rect> rects) {
139 Region result = Region.obtain();
140 if (rects != null) {
141 for (Rect r : rects) {
142 if (r != null && !r.isEmpty()) {
143 result.op(r, Region.Op.UNION);
144 }
145 }
146 }
147 return result;
148 }
149
Adrian Roos5b518852018-01-23 17:23:38 +0100150 @Override
151 public void start() {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200152 mHandler = startHandlerThread();
153 mHandler.post(this::startOnScreenDecorationsThread);
154 setupStatusBarPaddingIfNeeded();
Mark Renouf0255e162019-05-21 12:54:58 -0400155 putComponent(ScreenDecorations.class, this);
Matt Casey97193c22019-06-04 18:55:51 -0400156 mInGesturalMode = QuickStepContract.isGesturalMode(
157 Dependency.get(NavigationModeController.class)
158 .addListener(this::handleNavigationModeChange));
159 }
160
161 @VisibleForTesting
162 void handleNavigationModeChange(int navigationMode) {
163 if (!mHandler.getLooper().isCurrentThread()) {
164 mHandler.post(() -> handleNavigationModeChange(navigationMode));
165 return;
166 }
167 boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode);
168 if (mInGesturalMode != inGesturalMode) {
169 mInGesturalMode = inGesturalMode;
170
171 if (mInGesturalMode && mOverlay == null) {
172 setupDecorations();
173 if (mOverlay != null) {
174 updateLayoutParams();
175 }
176 }
177 }
Mark Renouf0255e162019-05-21 12:54:58 -0400178 }
179
Matt Casey9fedd812019-06-05 17:14:09 -0400180 /**
181 * Returns an animator that animates the given view from start to end over durationMs. Start and
182 * end represent total animation progress: 0 is the start, 1 is the end, 1.1 would be an
183 * overshoot.
184 */
185 Animator getHandleAnimator(View view, float start, float end, boolean isLeft, long durationMs,
186 Interpolator interpolator) {
187 // Note that lerp does allow overshoot, in cases where start and end are outside of [0,1].
188 float scaleStart = MathUtils.lerp(2f, 1f, start);
189 float scaleEnd = MathUtils.lerp(2f, 1f, end);
190 Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, scaleStart, scaleEnd);
191 Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, scaleStart, scaleEnd);
192 float translationStart = MathUtils.lerp(0.2f, 0f, start);
193 float translationEnd = MathUtils.lerp(0.2f, 0f, end);
194 int xDirection = isLeft ? -1 : 1;
195 Animator translateX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
196 xDirection * translationStart * view.getWidth(),
197 xDirection * translationEnd * view.getWidth());
198 Animator translateY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
199 translationStart * view.getHeight(), translationEnd * view.getHeight());
200
201 AnimatorSet set = new AnimatorSet();
202 set.play(scaleX).with(scaleY);
203 set.play(scaleX).with(translateX);
204 set.play(scaleX).with(translateY);
205 set.setDuration(durationMs);
206 set.setInterpolator(interpolator);
207 return set;
208 }
209
James O'Leary4335c702019-05-29 12:38:51 -0400210 private void fade(View view, boolean fadeIn, boolean isLeft) {
Mark Renouf0255e162019-05-21 12:54:58 -0400211 if (fadeIn) {
212 view.animate().cancel();
James O'Leary4335c702019-05-29 12:38:51 -0400213 view.setAlpha(1f);
Mark Renouf0255e162019-05-21 12:54:58 -0400214 view.setVisibility(View.VISIBLE);
James O'Leary4335c702019-05-29 12:38:51 -0400215
Matt Casey9fedd812019-06-05 17:14:09 -0400216 // A piecewise spring-like interpolation.
217 // End value in one animator call must match the start value in the next, otherwise
218 // there will be a discontinuity.
219 AnimatorSet anim = new AnimatorSet();
220 Animator first = getHandleAnimator(view, 0, 1.1f, isLeft, 750,
221 new PathInterpolator(0, 0.45f, .67f, 1f));
222 Interpolator secondInterpolator = new PathInterpolator(0.33f, 0, 0.67f, 1f);
223 Animator second = getHandleAnimator(view, 1.1f, 0.97f, isLeft, 400,
224 secondInterpolator);
225 Animator third = getHandleAnimator(view, 0.97f, 1.02f, isLeft, 400,
226 secondInterpolator);
227 Animator fourth = getHandleAnimator(view, 1.02f, 1f, isLeft, 400,
228 secondInterpolator);
229 anim.play(first).before(second);
230 anim.play(second).before(third);
231 anim.play(third).before(fourth);
232 anim.start();
Mark Renouf0255e162019-05-21 12:54:58 -0400233 } else {
234 view.animate().cancel();
Govinda Wasserman4f706c22019-06-12 14:41:36 -0400235 view.animate()
236 .setInterpolator(new AccelerateInterpolator(1.5f))
237 .setDuration(250)
238 .alpha(0f);
Mark Renouf0255e162019-05-21 12:54:58 -0400239 }
240
241 }
242
243 /**
244 * Controls the visibility of the assist gesture handles.
245 *
246 * @param visible whether the handles should be shown
247 */
248 public void setAssistHintVisible(boolean visible) {
Govinda Wassermanc7495cd2019-05-20 14:43:28 -0400249 if (!mHandler.getLooper().isCurrentThread()) {
250 mHandler.post(() -> setAssistHintVisible(visible));
251 return;
252 }
253
James O'Leary4335c702019-05-29 12:38:51 -0400254 if (mAssistHintBlocked && visible) {
255 if (VERBOSE) {
256 Log.v(TAG, "Assist hint blocked, cannot make it visible");
257 }
258 return;
259 }
260
Matt Casey603c6972019-06-03 19:21:34 -0400261 if (mOverlay == null || mBottomOverlay == null) {
262 return;
263 }
264
Mark Renouf0255e162019-05-21 12:54:58 -0400265 if (mAssistHintVisible != visible) {
266 mAssistHintVisible = visible;
267
James O'Leary4335c702019-05-29 12:38:51 -0400268 CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
269 CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
270 CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById(
271 R.id.assist_hint_left);
272 CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById(
273 R.id.assist_hint_right);
Mark Renouf0255e162019-05-21 12:54:58 -0400274
275 switch (mRotation) {
276 case RotationUtils.ROTATION_NONE:
James O'Leary4335c702019-05-29 12:38:51 -0400277 fade(assistHintBottomLeft, mAssistHintVisible, /* isLeft = */ true);
278 fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false);
Mark Renouf0255e162019-05-21 12:54:58 -0400279 break;
280 case RotationUtils.ROTATION_LANDSCAPE:
James O'Leary4335c702019-05-29 12:38:51 -0400281 fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true);
282 fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false);
Mark Renouf0255e162019-05-21 12:54:58 -0400283 break;
284 case RotationUtils.ROTATION_SEASCAPE:
James O'Leary4335c702019-05-29 12:38:51 -0400285 fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false);
286 fade(assistHintBottomLeft, mAssistHintVisible, /* isLeft = */ true);
Mark Renouf0255e162019-05-21 12:54:58 -0400287 break;
288 case RotationUtils.ROTATION_UPSIDE_DOWN:
James O'Leary4335c702019-05-29 12:38:51 -0400289 fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false);
290 fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true);
Mark Renouf0255e162019-05-21 12:54:58 -0400291 break;
292 }
293 }
Matt Casey97193c22019-06-04 18:55:51 -0400294 updateWindowVisibilities();
Adrian Roosfaa102f2018-08-02 15:56:15 +0200295 }
296
James O'Leary4335c702019-05-29 12:38:51 -0400297 /**
298 * Prevents the assist hint from becoming visible even if `mAssistHintVisible` is true.
299 */
300 public void setAssistHintBlocked(boolean blocked) {
301 if (!mHandler.getLooper().isCurrentThread()) {
302 mHandler.post(() -> setAssistHintBlocked(blocked));
303 return;
304 }
305
306 mAssistHintBlocked = blocked;
307 if (mAssistHintVisible && mAssistHintBlocked) {
Matt Caseyc9498da2019-06-19 09:53:35 -0400308 hideAssistHandles();
James O'Leary4335c702019-05-29 12:38:51 -0400309 }
310 }
311
Adrian Roosfaa102f2018-08-02 15:56:15 +0200312 @VisibleForTesting
313 Handler startHandlerThread() {
314 HandlerThread thread = new HandlerThread("ScreenDecorations");
315 thread.start();
316 return thread.getThreadHandler();
317 }
318
Matt Casey97193c22019-06-04 18:55:51 -0400319 private boolean shouldHostHandles() {
320 return mInGesturalMode;
321 }
322
Adrian Roosfaa102f2018-08-02 15:56:15 +0200323 private void startOnScreenDecorationsThread() {
324 mRotation = RotationUtils.getExactRotation(mContext);
Adrian Roos5b518852018-01-23 17:23:38 +0100325 mWindowManager = mContext.getSystemService(WindowManager.class);
Adrian Roos64c9d902018-08-20 13:43:38 +0200326 updateRoundedCornerRadii();
Matt Casey97193c22019-06-04 18:55:51 -0400327 if (hasRoundedCorners() || shouldDrawCutout() || shouldHostHandles()) {
Adrian Roos5b518852018-01-23 17:23:38 +0100328 setupDecorations();
329 }
Beverlya5f7a302018-04-25 09:19:05 -0400330
Beverlye91f0d02018-05-15 14:40:47 -0400331 mDisplayListener = new DisplayManager.DisplayListener() {
332 @Override
333 public void onDisplayAdded(int displayId) {
334 // do nothing
335 }
336
337 @Override
338 public void onDisplayRemoved(int displayId) {
339 // do nothing
340 }
341
342 @Override
343 public void onDisplayChanged(int displayId) {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200344 final int newRotation = RotationUtils.getExactRotation(mContext);
345 if (mOverlay != null && mBottomOverlay != null && mRotation != newRotation) {
Vishnu Nair83537a72018-07-19 21:27:48 -0700346 // We cannot immediately update the orientation. Otherwise
347 // WindowManager is still deferring layout until it has finished dispatching
348 // the config changes, which may cause divergence between what we draw
349 // (new orientation), and where we are placed on the screen (old orientation).
350 // Instead we wait until either:
351 // - we are trying to redraw. This because WM resized our window and told us to.
352 // - the config change has been dispatched, so WM is no longer deferring layout.
353 mPendingRotationChange = true;
Adrian Roosfaa102f2018-08-02 15:56:15 +0200354 if (DEBUG) {
355 Log.i(TAG, "Rotation changed, deferring " + newRotation + ", staying at "
356 + mRotation);
357 }
358
Vishnu Nair83537a72018-07-19 21:27:48 -0700359 mOverlay.getViewTreeObserver().addOnPreDrawListener(
Adrian Roosfaa102f2018-08-02 15:56:15 +0200360 new RestartingPreDrawListener(mOverlay, newRotation));
Vishnu Nair83537a72018-07-19 21:27:48 -0700361 mBottomOverlay.getViewTreeObserver().addOnPreDrawListener(
Adrian Roosfaa102f2018-08-02 15:56:15 +0200362 new RestartingPreDrawListener(mBottomOverlay, newRotation));
Vishnu Nair83537a72018-07-19 21:27:48 -0700363 }
Beverlye91f0d02018-05-15 14:40:47 -0400364 updateOrientation();
365 }
366 };
367
Beverlye91f0d02018-05-15 14:40:47 -0400368 mDisplayManager = (DisplayManager) mContext.getSystemService(
369 Context.DISPLAY_SERVICE);
Adrian Roosfaa102f2018-08-02 15:56:15 +0200370 mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
371 updateOrientation();
Adrian Roos5b518852018-01-23 17:23:38 +0100372 }
373
374 private void setupDecorations() {
375 mOverlay = LayoutInflater.from(mContext)
376 .inflate(R.layout.rounded_corners, null);
Adrian Roos9acf8132018-05-31 19:54:53 +0200377 mCutoutTop = new DisplayCutoutView(mContext, true,
Vishnu Nair83537a72018-07-19 21:27:48 -0700378 this::updateWindowVisibilities, this);
James O'Leary4335c702019-05-29 12:38:51 -0400379 ((ViewGroup) mOverlay).addView(mCutoutTop);
Adrian Roos5b518852018-01-23 17:23:38 +0100380 mBottomOverlay = LayoutInflater.from(mContext)
381 .inflate(R.layout.rounded_corners, null);
Adrian Roos9acf8132018-05-31 19:54:53 +0200382 mCutoutBottom = new DisplayCutoutView(mContext, false,
Vishnu Nair83537a72018-07-19 21:27:48 -0700383 this::updateWindowVisibilities, this);
James O'Leary4335c702019-05-29 12:38:51 -0400384 ((ViewGroup) mBottomOverlay).addView(mCutoutBottom);
Adrian Roos5b518852018-01-23 17:23:38 +0100385
386 mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
387 mOverlay.setAlpha(0);
John Recka2d20b42018-10-01 12:21:55 -0700388 mOverlay.setForceDarkAllowed(false);
Adrian Roos5b518852018-01-23 17:23:38 +0100389
390 mBottomOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
391 mBottomOverlay.setAlpha(0);
John Recka2d20b42018-10-01 12:21:55 -0700392 mBottomOverlay.setForceDarkAllowed(false);
Adrian Roos5b518852018-01-23 17:23:38 +0100393
394 updateViews();
395
396 mWindowManager.addView(mOverlay, getWindowLayoutParams());
397 mWindowManager.addView(mBottomOverlay, getBottomLayoutParams());
398
399 DisplayMetrics metrics = new DisplayMetrics();
400 mWindowManager.getDefaultDisplay().getMetrics(metrics);
401 mDensity = metrics.density;
402
Adrian Roosfaa102f2018-08-02 15:56:15 +0200403 Dependency.get(Dependency.MAIN_HANDLER).post(
404 () -> Dependency.get(TunerService.class).addTunable(this, SIZE));
Adrian Roos5b518852018-01-23 17:23:38 +0100405
406 // Watch color inversion and invert the overlay as needed.
Adrian Roosfaa102f2018-08-02 15:56:15 +0200407 mColorInversionSetting = new SecureSetting(mContext, mHandler,
Adrian Roos5b518852018-01-23 17:23:38 +0100408 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
409 @Override
410 protected void handleValueChanged(int value, boolean observedChange) {
Beverly374acde2018-06-13 10:49:26 -0400411 updateColorInversion(value);
Adrian Roos5b518852018-01-23 17:23:38 +0100412 }
413 };
Beverly374acde2018-06-13 10:49:26 -0400414 mColorInversionSetting.setListening(true);
415 mColorInversionSetting.onChange(false);
416
417 IntentFilter filter = new IntentFilter();
418 filter.addAction(Intent.ACTION_USER_SWITCHED);
Adrian Roosfaa102f2018-08-02 15:56:15 +0200419 mContext.registerReceiver(mIntentReceiver, filter, null /* permission */, mHandler);
Adrian Roos5b518852018-01-23 17:23:38 +0100420
421 mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
422 @Override
423 public void onLayoutChange(View v, int left, int top, int right, int bottom,
424 int oldLeft,
425 int oldTop, int oldRight, int oldBottom) {
426 mOverlay.removeOnLayoutChangeListener(this);
427 mOverlay.animate()
428 .alpha(1)
429 .setDuration(1000)
430 .start();
431 mBottomOverlay.animate()
432 .alpha(1)
433 .setDuration(1000)
434 .start();
435 }
436 });
Adrian Roosfaa102f2018-08-02 15:56:15 +0200437
438 mOverlay.getViewTreeObserver().addOnPreDrawListener(
439 new ValidatingPreDrawListener(mOverlay));
440 mBottomOverlay.getViewTreeObserver().addOnPreDrawListener(
441 new ValidatingPreDrawListener(mBottomOverlay));
Adrian Roos5b518852018-01-23 17:23:38 +0100442 }
443
Beverly374acde2018-06-13 10:49:26 -0400444 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
445 @Override
446 public void onReceive(Context context, Intent intent) {
447 String action = intent.getAction();
448 if (action.equals(Intent.ACTION_USER_SWITCHED)) {
449 int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
450 ActivityManager.getCurrentUser());
451 // update color inversion setting to the new user
452 mColorInversionSetting.setUserId(newUserId);
453 updateColorInversion(mColorInversionSetting.getValue());
454 }
455 }
456 };
457
458 private void updateColorInversion(int colorsInvertedValue) {
459 int tint = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
460 ColorStateList tintList = ColorStateList.valueOf(tint);
461 ((ImageView) mOverlay.findViewById(R.id.left)).setImageTintList(tintList);
462 ((ImageView) mOverlay.findViewById(R.id.right)).setImageTintList(tintList);
463 ((ImageView) mBottomOverlay.findViewById(R.id.left)).setImageTintList(tintList);
464 ((ImageView) mBottomOverlay.findViewById(R.id.right)).setImageTintList(tintList);
465 mCutoutTop.setColor(tint);
466 mCutoutBottom.setColor(tint);
467 }
468
Adrian Roos5b518852018-01-23 17:23:38 +0100469 @Override
470 protected void onConfigurationChanged(Configuration newConfig) {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200471 mHandler.post(() -> {
472 int oldRotation = mRotation;
473 mPendingRotationChange = false;
474 updateOrientation();
Adrian Roos64c9d902018-08-20 13:43:38 +0200475 updateRoundedCornerRadii();
Adrian Roosfaa102f2018-08-02 15:56:15 +0200476 if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
477 if (shouldDrawCutout() && mOverlay == null) {
478 setupDecorations();
479 }
480 if (mOverlay != null) {
481 // Updating the layout params ensures that ViewRootImpl will call relayoutWindow(),
482 // which ensures that the forced seamless rotation will end, even if we updated
483 // the rotation before window manager was ready (and was still waiting for sending
484 // the updated rotation).
485 updateLayoutParams();
486 }
487 });
Beverlye91f0d02018-05-15 14:40:47 -0400488 }
489
Adrian Roosfaa102f2018-08-02 15:56:15 +0200490 private void updateOrientation() {
491 Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
492 "must call on " + mHandler.getLooper().getThread()
493 + ", but was " + Thread.currentThread());
Vishnu Nair83537a72018-07-19 21:27:48 -0700494 if (mPendingRotationChange) {
495 return;
496 }
Beverlye91f0d02018-05-15 14:40:47 -0400497 int newRotation = RotationUtils.getExactRotation(mContext);
498 if (newRotation != mRotation) {
499 mRotation = newRotation;
Adrian Roos5b518852018-01-23 17:23:38 +0100500
501 if (mOverlay != null) {
502 updateLayoutParams();
503 updateViews();
Matt Caseybcef5572019-05-29 12:13:00 -0400504 if (mAssistHintVisible) {
505 // If assist handles are visible, hide them without animation and then make them
506 // show once again (with corrected rotation).
507 hideAssistHandles();
508 setAssistHintVisible(true);
509 }
Adrian Roos5b518852018-01-23 17:23:38 +0100510 }
Adrian Roos5b518852018-01-23 17:23:38 +0100511 }
512 }
513
Matt Caseybcef5572019-05-29 12:13:00 -0400514 private void hideAssistHandles() {
515 if (mOverlay != null && mBottomOverlay != null) {
516 mOverlay.findViewById(R.id.assist_hint_left).setVisibility(View.GONE);
517 mOverlay.findViewById(R.id.assist_hint_right).setVisibility(View.GONE);
518 mBottomOverlay.findViewById(R.id.assist_hint_left).setVisibility(View.GONE);
519 mBottomOverlay.findViewById(R.id.assist_hint_right).setVisibility(View.GONE);
520 mAssistHintVisible = false;
521 }
522 }
523
Adrian Roos64c9d902018-08-20 13:43:38 +0200524 private void updateRoundedCornerRadii() {
525 final int newRoundedDefault = mContext.getResources().getDimensionPixelSize(
Beverly4d1113d2019-01-03 14:45:10 -0500526 com.android.internal.R.dimen.rounded_corner_radius);
Adrian Roos64c9d902018-08-20 13:43:38 +0200527 final int newRoundedDefaultTop = mContext.getResources().getDimensionPixelSize(
Beverly4d1113d2019-01-03 14:45:10 -0500528 com.android.internal.R.dimen.rounded_corner_radius_top);
Adrian Roos64c9d902018-08-20 13:43:38 +0200529 final int newRoundedDefaultBottom = mContext.getResources().getDimensionPixelSize(
Beverly4d1113d2019-01-03 14:45:10 -0500530 com.android.internal.R.dimen.rounded_corner_radius_bottom);
Adrian Roos64c9d902018-08-20 13:43:38 +0200531
532 final boolean roundedCornersChanged = mRoundedDefault != newRoundedDefault
533 || mRoundedDefaultBottom != newRoundedDefaultBottom
534 || mRoundedDefaultTop != newRoundedDefaultTop;
535
536 if (roundedCornersChanged) {
537 mRoundedDefault = newRoundedDefault;
538 mRoundedDefaultTop = newRoundedDefaultTop;
539 mRoundedDefaultBottom = newRoundedDefaultBottom;
540 onTuningChanged(SIZE, null);
541 }
542 }
543
Adrian Roos5b518852018-01-23 17:23:38 +0100544 private void updateViews() {
545 View topLeft = mOverlay.findViewById(R.id.left);
546 View topRight = mOverlay.findViewById(R.id.right);
547 View bottomLeft = mBottomOverlay.findViewById(R.id.left);
548 View bottomRight = mBottomOverlay.findViewById(R.id.right);
Beverlye91f0d02018-05-15 14:40:47 -0400549
550 if (mRotation == RotationUtils.ROTATION_NONE) {
551 updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
552 updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
553 updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
554 updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
555 } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
556 updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
557 updateView(topRight, Gravity.BOTTOM | Gravity.LEFT, 270);
James O'Leary4335c702019-05-29 12:38:51 -0400558 updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);
Beverlye91f0d02018-05-15 14:40:47 -0400559 updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
560 } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
561 updateView(topLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
562 updateView(topRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
563 updateView(bottomLeft, Gravity.TOP | Gravity.LEFT, 0);
564 updateView(bottomRight, Gravity.TOP | Gravity.RIGHT, 90);
565 } else if (mRotation == RotationUtils.ROTATION_SEASCAPE) {
566 updateView(topLeft, Gravity.BOTTOM | Gravity.RIGHT, 180);
567 updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
568 updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
569 updateView(bottomRight, Gravity.TOP | Gravity.LEFT, 0);
Adrian Roos5b518852018-01-23 17:23:38 +0100570 }
Adrian Roos5b518852018-01-23 17:23:38 +0100571
Mark Renouf0255e162019-05-21 12:54:58 -0400572 updateAssistantHandleViews();
Adrian Roos9acf8132018-05-31 19:54:53 +0200573 mCutoutTop.setRotation(mRotation);
574 mCutoutBottom.setRotation(mRotation);
575
Adrian Roos5b518852018-01-23 17:23:38 +0100576 updateWindowVisibilities();
577 }
578
Mark Renouf0255e162019-05-21 12:54:58 -0400579 private void updateAssistantHandleViews() {
580 View assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
581 View assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
582 View assistHintBottomLeft = mBottomOverlay.findViewById(R.id.assist_hint_left);
583 View assistHintBottomRight = mBottomOverlay.findViewById(R.id.assist_hint_right);
584
585 final int assistHintVisibility = mAssistHintVisible ? View.VISIBLE : View.INVISIBLE;
586
587 if (mRotation == RotationUtils.ROTATION_NONE) {
588 assistHintTopLeft.setVisibility(View.GONE);
589 assistHintTopRight.setVisibility(View.GONE);
590 assistHintBottomLeft.setVisibility(assistHintVisibility);
591 assistHintBottomRight.setVisibility(assistHintVisibility);
592 updateView(assistHintBottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
593 updateView(assistHintBottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
594 } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
595 assistHintTopLeft.setVisibility(View.GONE);
596 assistHintTopRight.setVisibility(assistHintVisibility);
597 assistHintBottomLeft.setVisibility(View.GONE);
598 assistHintBottomRight.setVisibility(assistHintVisibility);
599 updateView(assistHintTopRight, Gravity.BOTTOM | Gravity.LEFT, 270);
600 updateView(assistHintBottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
601 } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
602 assistHintTopLeft.setVisibility(assistHintVisibility);
603 assistHintTopRight.setVisibility(assistHintVisibility);
604 assistHintBottomLeft.setVisibility(View.GONE);
605 assistHintBottomRight.setVisibility(View.GONE);
606 updateView(assistHintTopLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
607 updateView(assistHintTopRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
608 } else if (mRotation == RotationUtils.ROTATION_SEASCAPE) {
609 assistHintTopLeft.setVisibility(assistHintVisibility);
610 assistHintTopRight.setVisibility(View.GONE);
611 assistHintBottomLeft.setVisibility(assistHintVisibility);
612 assistHintBottomRight.setVisibility(View.GONE);
613 updateView(assistHintTopLeft, Gravity.BOTTOM | Gravity.RIGHT, 180);
614 updateView(assistHintBottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
615 }
616 }
617
Adrian Roos5b518852018-01-23 17:23:38 +0100618 private void updateView(View v, int gravity, int rotation) {
James O'Leary4335c702019-05-29 12:38:51 -0400619 ((FrameLayout.LayoutParams) v.getLayoutParams()).gravity = gravity;
Adrian Roos5b518852018-01-23 17:23:38 +0100620 v.setRotation(rotation);
621 }
622
623 private void updateWindowVisibilities() {
624 updateWindowVisibility(mOverlay);
625 updateWindowVisibility(mBottomOverlay);
626 }
627
628 private void updateWindowVisibility(View overlay) {
629 boolean visibleForCutout = shouldDrawCutout()
630 && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE;
Beverlya5f7a302018-04-25 09:19:05 -0400631 boolean visibleForRoundedCorners = hasRoundedCorners();
Matt Casey97193c22019-06-04 18:55:51 -0400632 boolean visibleForHandles = overlay.findViewById(R.id.assist_hint_left).getVisibility()
633 == View.VISIBLE || overlay.findViewById(R.id.assist_hint_right).getVisibility()
634 == View.VISIBLE;
635 overlay.setVisibility(visibleForCutout || visibleForRoundedCorners || visibleForHandles
Adrian Roos5b518852018-01-23 17:23:38 +0100636 ? View.VISIBLE : View.GONE);
637 }
638
Beverlya5f7a302018-04-25 09:19:05 -0400639 private boolean hasRoundedCorners() {
640 return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0;
641 }
642
Adrian Roos5b518852018-01-23 17:23:38 +0100643 private boolean shouldDrawCutout() {
Adrian Roosc41b32b2018-04-12 10:13:48 +0200644 return shouldDrawCutout(mContext);
645 }
646
647 static boolean shouldDrawCutout(Context context) {
648 return context.getResources().getBoolean(
Adrian Roos5b518852018-01-23 17:23:38 +0100649 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
650 }
651
Adrian Roosfaa102f2018-08-02 15:56:15 +0200652
653 private void setupStatusBarPaddingIfNeeded() {
654 // TODO: This should be moved to a more appropriate place, as it is not related to the
655 // screen decorations overlay.
656 int padding = mContext.getResources().getDimensionPixelSize(
657 R.dimen.rounded_corner_content_padding);
658 if (padding != 0) {
659 setupStatusBarPadding(padding);
660 }
661
662 }
663
664 private void setupStatusBarPadding(int padding) {
Adrian Roos5b518852018-01-23 17:23:38 +0100665 // Add some padding to all the content near the edge of the screen.
666 StatusBar sb = getComponent(StatusBar.class);
667 View statusBar = (sb != null ? sb.getStatusBarWindow() : null);
668 if (statusBar != null) {
669 TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING,
670 padding, FLAG_END);
671
672 FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar);
673 fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
674 new TunablePaddingTagListener(padding, R.id.status_bar));
675 fragmentHostManager.addTagListener(QS.TAG,
676 new TunablePaddingTagListener(padding, R.id.header));
677 }
678 }
679
680 @VisibleForTesting
681 WindowManager.LayoutParams getWindowLayoutParams() {
682 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
683 ViewGroup.LayoutParams.MATCH_PARENT,
684 LayoutParams.WRAP_CONTENT,
685 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
Evan Lairdb0506ca2018-03-15 10:34:31 -0400686 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
Adrian Roos5b518852018-01-23 17:23:38 +0100687 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
688 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
689 | WindowManager.LayoutParams.FLAG_SLIPPERY
690 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
691 PixelFormat.TRANSLUCENT);
Robert Carr772e8bc2018-03-14 11:51:23 -0700692 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
693 | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
694
Adrian Roos56d1a2c2018-03-08 23:22:19 +0100695 if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
696 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
697 }
Robert Carr772e8bc2018-03-14 11:51:23 -0700698
Adrian Roos5b518852018-01-23 17:23:38 +0100699 lp.setTitle("ScreenDecorOverlay");
Beverlye91f0d02018-05-15 14:40:47 -0400700 if (mRotation == RotationUtils.ROTATION_SEASCAPE
701 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
702 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
703 } else {
704 lp.gravity = Gravity.TOP | Gravity.LEFT;
705 }
Adrian Roos5b518852018-01-23 17:23:38 +0100706 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
Beverlye91f0d02018-05-15 14:40:47 -0400707 if (isLandscape(mRotation)) {
Adrian Roos5b518852018-01-23 17:23:38 +0100708 lp.width = WRAP_CONTENT;
709 lp.height = MATCH_PARENT;
710 }
Peiyong Lin75045382019-03-04 19:22:33 -0800711 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
Adrian Roos5b518852018-01-23 17:23:38 +0100712 return lp;
713 }
714
715 private WindowManager.LayoutParams getBottomLayoutParams() {
716 WindowManager.LayoutParams lp = getWindowLayoutParams();
717 lp.setTitle("ScreenDecorOverlayBottom");
Beverlye91f0d02018-05-15 14:40:47 -0400718 if (mRotation == RotationUtils.ROTATION_SEASCAPE
719 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
720 lp.gravity = Gravity.TOP | Gravity.LEFT;
721 } else {
722 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
723 }
Adrian Roos5b518852018-01-23 17:23:38 +0100724 return lp;
725 }
726
727 private void updateLayoutParams() {
728 mWindowManager.updateViewLayout(mOverlay, getWindowLayoutParams());
729 mWindowManager.updateViewLayout(mBottomOverlay, getBottomLayoutParams());
730 }
731
732 @Override
733 public void onTuningChanged(String key, String newValue) {
Adrian Roosfaa102f2018-08-02 15:56:15 +0200734 mHandler.post(() -> {
735 if (mOverlay == null) return;
736 if (SIZE.equals(key)) {
737 int size = mRoundedDefault;
738 int sizeTop = mRoundedDefaultTop;
739 int sizeBottom = mRoundedDefaultBottom;
740 if (newValue != null) {
741 try {
742 size = (int) (Integer.parseInt(newValue) * mDensity);
743 } catch (Exception e) {
744 }
Beverlya5f7a302018-04-25 09:19:05 -0400745 }
Adrian Roos72940642018-08-09 15:23:56 +0000746
Adrian Roosfaa102f2018-08-02 15:56:15 +0200747 if (sizeTop == 0) {
748 sizeTop = size;
749 }
750 if (sizeBottom == 0) {
751 sizeBottom = size;
752 }
Adrian Roos72940642018-08-09 15:23:56 +0000753
Adrian Roosfaa102f2018-08-02 15:56:15 +0200754 setSize(mOverlay.findViewById(R.id.left), sizeTop);
755 setSize(mOverlay.findViewById(R.id.right), sizeTop);
756 setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom);
757 setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom);
758 }
759 });
Adrian Roos5b518852018-01-23 17:23:38 +0100760 }
761
762 private void setSize(View view, int pixelSize) {
763 LayoutParams params = view.getLayoutParams();
764 params.width = pixelSize;
765 params.height = pixelSize;
766 view.setLayoutParams(params);
767 }
768
James O'Leary4335c702019-05-29 12:38:51 -0400769 @Override
770 public void onDarkIntensity(float darkIntensity) {
Matt Casey0f1babb2019-06-13 15:24:58 -0400771 if (!mHandler.getLooper().isCurrentThread()) {
772 mHandler.post(() -> onDarkIntensity(darkIntensity));
773 return;
774 }
James O'Leary4335c702019-05-29 12:38:51 -0400775 if (mOverlay != null) {
776 CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
777 CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
778
779 assistHintTopLeft.updateDarkness(darkIntensity);
780 assistHintTopRight.updateDarkness(darkIntensity);
781 }
782
783 if (mBottomOverlay != null) {
784 CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById(
785 R.id.assist_hint_left);
786 CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById(
787 R.id.assist_hint_right);
788
789 assistHintBottomLeft.updateDarkness(darkIntensity);
790 assistHintBottomRight.updateDarkness(darkIntensity);
791 }
792 }
793
Adrian Roos5b518852018-01-23 17:23:38 +0100794 @VisibleForTesting
795 static class TunablePaddingTagListener implements FragmentListener {
796
797 private final int mPadding;
798 private final int mId;
799 private TunablePadding mTunablePadding;
800
801 public TunablePaddingTagListener(int padding, int id) {
802 mPadding = padding;
803 mId = id;
804 }
805
806 @Override
807 public void onFragmentViewCreated(String tag, Fragment fragment) {
808 if (mTunablePadding != null) {
809 mTunablePadding.destroy();
810 }
811 View view = fragment.getView();
812 if (mId != 0) {
813 view = view.findViewById(mId);
814 }
815 mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding,
816 FLAG_START | FLAG_END);
817 }
818 }
819
Evan Lairdb0506ca2018-03-15 10:34:31 -0400820 public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener,
821 RegionInterceptableView {
Adrian Roos5b518852018-01-23 17:23:38 +0100822
823 private final DisplayInfo mInfo = new DisplayInfo();
824 private final Paint mPaint = new Paint();
Issei Suzuki43190bd2018-08-20 17:28:41 +0200825 private final List<Rect> mBounds = new ArrayList();
Adrian Roos5b518852018-01-23 17:23:38 +0100826 private final Rect mBoundingRect = new Rect();
827 private final Path mBoundingPath = new Path();
828 private final int[] mLocation = new int[2];
Adrian Roos9acf8132018-05-31 19:54:53 +0200829 private final boolean mInitialStart;
Adrian Roos5b518852018-01-23 17:23:38 +0100830 private final Runnable mVisibilityChangedListener;
Vishnu Nair83537a72018-07-19 21:27:48 -0700831 private final ScreenDecorations mDecorations;
Adrian Roos8b29a842018-05-31 14:14:13 +0200832 private int mColor = Color.BLACK;
Adrian Roos9acf8132018-05-31 19:54:53 +0200833 private boolean mStart;
834 private int mRotation;
Adrian Roos5b518852018-01-23 17:23:38 +0100835
836 public DisplayCutoutView(Context context, boolean start,
Vishnu Nair83537a72018-07-19 21:27:48 -0700837 Runnable visibilityChangedListener, ScreenDecorations decorations) {
Adrian Roos5b518852018-01-23 17:23:38 +0100838 super(context);
Adrian Roos9acf8132018-05-31 19:54:53 +0200839 mInitialStart = start;
Adrian Roos5b518852018-01-23 17:23:38 +0100840 mVisibilityChangedListener = visibilityChangedListener;
Vishnu Nair83537a72018-07-19 21:27:48 -0700841 mDecorations = decorations;
Adrian Roos5b518852018-01-23 17:23:38 +0100842 setId(R.id.display_cutout);
Adrian Roosfaa102f2018-08-02 15:56:15 +0200843 if (DEBUG) {
844 getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG,
845 (mInitialStart ? "OverlayTop" : "OverlayBottom")
846 + " drawn in rot " + mRotation));
847 }
Adrian Roos5b518852018-01-23 17:23:38 +0100848 }
849
Adrian Roos8b29a842018-05-31 14:14:13 +0200850 public void setColor(int color) {
851 mColor = color;
852 invalidate();
853 }
854
Adrian Roos5b518852018-01-23 17:23:38 +0100855 @Override
856 protected void onAttachedToWindow() {
857 super.onAttachedToWindow();
858 mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
859 getHandler());
860 update();
861 }
862
863 @Override
864 protected void onDetachedFromWindow() {
865 super.onDetachedFromWindow();
866 mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
867 }
868
869 @Override
870 protected void onDraw(Canvas canvas) {
871 super.onDraw(canvas);
872 getLocationOnScreen(mLocation);
873 canvas.translate(-mLocation[0], -mLocation[1]);
874 if (!mBoundingPath.isEmpty()) {
Adrian Roos8b29a842018-05-31 14:14:13 +0200875 mPaint.setColor(mColor);
Adrian Roos5b518852018-01-23 17:23:38 +0100876 mPaint.setStyle(Paint.Style.FILL);
Adrian Roos51072a82018-04-10 15:17:08 -0700877 mPaint.setAntiAlias(true);
Adrian Roos5b518852018-01-23 17:23:38 +0100878 canvas.drawPath(mBoundingPath, mPaint);
879 }
880 }
881
882 @Override
883 public void onDisplayAdded(int displayId) {
884 }
885
886 @Override
887 public void onDisplayRemoved(int displayId) {
888 }
889
890 @Override
891 public void onDisplayChanged(int displayId) {
892 if (displayId == getDisplay().getDisplayId()) {
893 update();
894 }
895 }
896
Adrian Roos9acf8132018-05-31 19:54:53 +0200897 public void setRotation(int rotation) {
898 mRotation = rotation;
899 update();
900 }
901
902 private boolean isStart() {
903 final boolean flipped = (mRotation == RotationUtils.ROTATION_SEASCAPE
904 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN);
905 return flipped ? !mInitialStart : mInitialStart;
906 }
907
Adrian Roos5b518852018-01-23 17:23:38 +0100908 private void update() {
Vishnu Nair83537a72018-07-19 21:27:48 -0700909 if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) {
Adrian Roos9acf8132018-05-31 19:54:53 +0200910 return;
911 }
Vishnu Nair83537a72018-07-19 21:27:48 -0700912 mStart = isStart();
Adrian Roos5b518852018-01-23 17:23:38 +0100913 requestLayout();
914 getDisplay().getDisplayInfo(mInfo);
Issei Suzuki43190bd2018-08-20 17:28:41 +0200915 mBounds.clear();
Adrian Roos5b518852018-01-23 17:23:38 +0100916 mBoundingRect.setEmpty();
917 mBoundingPath.reset();
918 int newVisible;
Adrian Roosc41b32b2018-04-12 10:13:48 +0200919 if (shouldDrawCutout(getContext()) && hasCutout()) {
Issei Suzuki43190bd2018-08-20 17:28:41 +0200920 mBounds.addAll(mInfo.displayCutout.getBoundingRects());
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100921 localBounds(mBoundingRect);
Adrian Roos0098aee2019-06-03 16:47:02 +0200922 updateGravity();
Adrian Roos51072a82018-04-10 15:17:08 -0700923 updateBoundingPath();
Adrian Roos1b028282018-03-14 14:43:03 +0100924 invalidate();
Adrian Roos5b518852018-01-23 17:23:38 +0100925 newVisible = VISIBLE;
926 } else {
927 newVisible = GONE;
928 }
929 if (newVisible != getVisibility()) {
930 setVisibility(newVisible);
931 mVisibilityChangedListener.run();
932 }
933 }
934
Adrian Roos51072a82018-04-10 15:17:08 -0700935 private void updateBoundingPath() {
936 int lw = mInfo.logicalWidth;
937 int lh = mInfo.logicalHeight;
938
939 boolean flipped = mInfo.rotation == ROTATION_90 || mInfo.rotation == ROTATION_270;
940
941 int dw = flipped ? lh : lw;
942 int dh = flipped ? lw : lh;
943
Adrian Roosa99f5d62018-04-16 16:03:04 +0200944 mBoundingPath.set(DisplayCutout.pathFromResources(getResources(), dw, dh));
Adrian Roos51072a82018-04-10 15:17:08 -0700945 Matrix m = new Matrix();
946 transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
947 mBoundingPath.transform(m);
948 }
949
950 private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation,
951 @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
952 switch (rotation) {
953 case ROTATION_0:
954 out.reset();
955 break;
956 case ROTATION_90:
957 out.setRotate(270);
958 out.postTranslate(0, physicalWidth);
959 break;
960 case ROTATION_180:
961 out.setRotate(180);
962 out.postTranslate(physicalWidth, physicalHeight);
963 break;
964 case ROTATION_270:
965 out.setRotate(90);
966 out.postTranslate(physicalHeight, 0);
967 break;
968 default:
969 throw new IllegalArgumentException("Unknown rotation: " + rotation);
970 }
971 }
972
Adrian Roos0098aee2019-06-03 16:47:02 +0200973 private void updateGravity() {
974 LayoutParams lp = getLayoutParams();
975 if (lp instanceof FrameLayout.LayoutParams) {
976 FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) lp;
977 int newGravity = getGravity(mInfo.displayCutout);
978 if (flp.gravity != newGravity) {
979 flp.gravity = newGravity;
980 setLayoutParams(flp);
981 }
982 }
983 }
984
Adrian Roos5b518852018-01-23 17:23:38 +0100985 private boolean hasCutout() {
Adrian Roos24264212018-02-19 16:26:15 +0100986 final DisplayCutout displayCutout = mInfo.displayCutout;
987 if (displayCutout == null) {
Adrian Roos5b518852018-01-23 17:23:38 +0100988 return false;
989 }
Adrian Roos5b518852018-01-23 17:23:38 +0100990 if (mStart) {
991 return displayCutout.getSafeInsetLeft() > 0
992 || displayCutout.getSafeInsetTop() > 0;
993 } else {
994 return displayCutout.getSafeInsetRight() > 0
995 || displayCutout.getSafeInsetBottom() > 0;
996 }
997 }
998
999 @Override
1000 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001001 if (mBounds.isEmpty()) {
Adrian Roos5b518852018-01-23 17:23:38 +01001002 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1003 return;
1004 }
1005 setMeasuredDimension(
1006 resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
1007 resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
1008 }
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001009
Jorim Jaggi71aaa402018-06-06 17:22:56 +02001010 public static void boundsFromDirection(DisplayCutout displayCutout, int gravity,
1011 Rect out) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001012 switch (gravity) {
1013 case Gravity.TOP:
Issei Suzuki43190bd2018-08-20 17:28:41 +02001014 out.set(displayCutout.getBoundingRectTop());
Evan Laird6703f422018-10-11 13:24:05 -04001015 break;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001016 case Gravity.LEFT:
Issei Suzuki43190bd2018-08-20 17:28:41 +02001017 out.set(displayCutout.getBoundingRectLeft());
Evan Laird6703f422018-10-11 13:24:05 -04001018 break;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001019 case Gravity.BOTTOM:
Issei Suzuki43190bd2018-08-20 17:28:41 +02001020 out.set(displayCutout.getBoundingRectBottom());
Evan Laird6703f422018-10-11 13:24:05 -04001021 break;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001022 case Gravity.RIGHT:
Issei Suzuki43190bd2018-08-20 17:28:41 +02001023 out.set(displayCutout.getBoundingRectRight());
Evan Laird6703f422018-10-11 13:24:05 -04001024 break;
1025 default:
1026 out.setEmpty();
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001027 }
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001028 }
1029
1030 private void localBounds(Rect out) {
Adrian Roos0098aee2019-06-03 16:47:02 +02001031 DisplayCutout displayCutout = mInfo.displayCutout;
1032 boundsFromDirection(displayCutout, getGravity(displayCutout), out);
1033 }
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001034
Adrian Roos0098aee2019-06-03 16:47:02 +02001035 private int getGravity(DisplayCutout displayCutout) {
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001036 if (mStart) {
1037 if (displayCutout.getSafeInsetLeft() > 0) {
Adrian Roos0098aee2019-06-03 16:47:02 +02001038 return Gravity.LEFT;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001039 } else if (displayCutout.getSafeInsetTop() > 0) {
Adrian Roos0098aee2019-06-03 16:47:02 +02001040 return Gravity.TOP;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001041 }
1042 } else {
1043 if (displayCutout.getSafeInsetRight() > 0) {
Adrian Roos0098aee2019-06-03 16:47:02 +02001044 return Gravity.RIGHT;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001045 } else if (displayCutout.getSafeInsetBottom() > 0) {
Adrian Roos0098aee2019-06-03 16:47:02 +02001046 return Gravity.BOTTOM;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001047 }
1048 }
Adrian Roos0098aee2019-06-03 16:47:02 +02001049 return Gravity.NO_GRAVITY;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +01001050 }
Evan Lairdb0506ca2018-03-15 10:34:31 -04001051
1052 @Override
1053 public boolean shouldInterceptTouch() {
1054 return mInfo.displayCutout != null && getVisibility() == VISIBLE;
1055 }
1056
1057 @Override
1058 public Region getInterceptRegion() {
1059 if (mInfo.displayCutout == null) {
1060 return null;
1061 }
1062
Adrian Roos134e1cb2018-05-16 17:04:29 +02001063 View rootView = getRootView();
Issei Suzuki43190bd2018-08-20 17:28:41 +02001064 Region cutoutBounds = rectsToRegion(
1065 mInfo.displayCutout.getBoundingRects());
Adrian Roos134e1cb2018-05-16 17:04:29 +02001066
1067 // Transform to window's coordinate space
1068 rootView.getLocationOnScreen(mLocation);
1069 cutoutBounds.translate(-mLocation[0], -mLocation[1]);
1070
1071 // Intersect with window's frame
1072 cutoutBounds.op(rootView.getLeft(), rootView.getTop(), rootView.getRight(),
1073 rootView.getBottom(), Region.Op.INTERSECT);
1074
1075 return cutoutBounds;
Evan Lairdb0506ca2018-03-15 10:34:31 -04001076 }
Adrian Roos5b518852018-01-23 17:23:38 +01001077 }
Beverlye91f0d02018-05-15 14:40:47 -04001078
1079 private boolean isLandscape(int rotation) {
1080 return rotation == RotationUtils.ROTATION_LANDSCAPE || rotation ==
1081 RotationUtils.ROTATION_SEASCAPE;
1082 }
Vishnu Nair83537a72018-07-19 21:27:48 -07001083
1084 /**
1085 * A pre-draw listener, that cancels the draw and restarts the traversal with the updated
1086 * window attributes.
1087 */
1088 private class RestartingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
1089
1090 private final View mView;
Adrian Roosfaa102f2018-08-02 15:56:15 +02001091 private final int mTargetRotation;
Vishnu Nair83537a72018-07-19 21:27:48 -07001092
Adrian Roosfaa102f2018-08-02 15:56:15 +02001093 private RestartingPreDrawListener(View view, int targetRotation) {
1094 mView = view;
1095 mTargetRotation = targetRotation;
1096 }
1097
1098 @Override
1099 public boolean onPreDraw() {
1100 mView.getViewTreeObserver().removeOnPreDrawListener(this);
1101
1102 if (mTargetRotation == mRotation) {
1103 if (DEBUG) {
1104 Log.i(TAG, (mView == mOverlay ? "OverlayTop" : "OverlayBottom")
1105 + " already in target rot "
1106 + mTargetRotation + ", allow draw without restarting it");
1107 }
1108 return true;
1109 }
1110
1111 mPendingRotationChange = false;
1112 // This changes the window attributes - we need to restart the traversal for them to
1113 // take effect.
1114 updateOrientation();
1115 if (DEBUG) {
1116 Log.i(TAG, (mView == mOverlay ? "OverlayTop" : "OverlayBottom")
1117 + " restarting listener fired, restarting draw for rot " + mRotation);
1118 }
1119 mView.invalidate();
1120 return false;
1121 }
1122 }
1123
1124 /**
1125 * A pre-draw listener, that validates that the rotation we draw in matches the displays
1126 * rotation before continuing the draw.
1127 *
1128 * This is to prevent a race condition, where we have not received the display changed event
1129 * yet, and would thus draw in an old orientation.
1130 */
1131 private class ValidatingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
1132
1133 private final View mView;
1134
1135 public ValidatingPreDrawListener(View view) {
Adrian Roos61f557a2018-08-02 15:56:15 +02001136 mView = view;
Adrian Roos61f557a2018-08-02 15:56:15 +02001137 }
1138
1139 @Override
1140 public boolean onPreDraw() {
Adrian Roosfaa102f2018-08-02 15:56:15 +02001141 final int displayRotation = RotationUtils.getExactRotation(mContext);
1142 if (displayRotation != mRotation && !mPendingRotationChange) {
1143 if (DEBUG) {
1144 Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
1145 + displayRotation + ". Restarting draw");
1146 }
1147 mView.invalidate();
1148 return false;
1149 }
1150 return true;
Adrian Roos61f557a2018-08-02 15:56:15 +02001151 }
1152 }
Adrian Roos5b518852018-01-23 17:23:38 +01001153}