diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 27ffcee2a2dc6..d16082915207d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -506,4 +506,21 @@
- @*android:string/status_bar_headset
+
+
+
+
+
+
+
+ false
+
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
new file mode 100644
index 0000000000000..24fa91b9e8385
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui
+
+import android.content.Context
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.hardware.camera2.CameraManager
+import android.util.PathParser
+import java.util.concurrent.Executor
+
+import kotlin.math.roundToInt
+
+const val TAG = "CameraOpTransitionController"
+
+/**
+ * Listens for usage of the Camera and controls the ScreenDecorations transition to show extra
+ * protection around a display cutout based on config_frontBuiltInDisplayCutoutProtection and
+ * config_enableDisplayCutoutProtection
+ */
+class CameraAvailabilityListener(
+ private val cameraManager: CameraManager,
+ private val cutoutProtectionPath: Path,
+ private val targetCameraId: String,
+ private val executor: Executor
+) {
+ private var cutoutBounds = Rect()
+ private val listeners = mutableListOf()
+ private val availabilityCallback: CameraManager.AvailabilityCallback =
+ object : CameraManager.AvailabilityCallback() {
+ override fun onCameraAvailable(cameraId: String) {
+ if (targetCameraId == cameraId) {
+ notifyCameraInactive()
+ }
+ }
+
+ override fun onCameraUnavailable(cameraId: String) {
+ if (targetCameraId == cameraId) {
+ notifyCameraActive()
+ }
+ }
+ }
+
+ init {
+ val computed = RectF()
+ cutoutProtectionPath.computeBounds(computed, false /* unused */)
+ cutoutBounds.set(
+ computed.left.roundToInt(),
+ computed.top.roundToInt(),
+ computed.right.roundToInt(),
+ computed.bottom.roundToInt())
+ }
+
+ /**
+ * Start listening for availability events, and maybe notify listeners
+ *
+ * @return true if we started listening
+ */
+ fun startListening() {
+ registerCameraListener()
+ }
+
+ fun stop() {
+ unregisterCameraListener()
+ }
+
+ fun addTransitionCallback(callback: CameraTransitionCallback) {
+ listeners.add(callback)
+ }
+
+ fun removeTransitionCallback(callback: CameraTransitionCallback) {
+ listeners.remove(callback)
+ }
+
+ private fun registerCameraListener() {
+ cameraManager.registerAvailabilityCallback(executor, availabilityCallback)
+ }
+
+ private fun unregisterCameraListener() {
+ cameraManager.unregisterAvailabilityCallback(availabilityCallback)
+ }
+
+ private fun notifyCameraActive() {
+ listeners.forEach { it.onApplyCameraProtection(cutoutProtectionPath, cutoutBounds) }
+ }
+
+ private fun notifyCameraInactive() {
+ listeners.forEach { it.onHideCameraProtection() }
+ }
+
+ /**
+ * Callbacks to tell a listener that a relevant camera turned on and off.
+ */
+ interface CameraTransitionCallback {
+ fun onApplyCameraProtection(protectionPath: Path, bounds: Rect)
+ fun onHideCameraProtection()
+ }
+
+ companion object Factory {
+ fun build(context: Context, executor: Executor): CameraAvailabilityListener {
+ val manager = context
+ .getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ val res = context.resources
+ val pathString = res.getString(R.string.config_frontBuiltInDisplayCutoutProtection)
+ val cameraId = res.getString(R.string.config_protectedCameraId)
+
+ return CameraAvailabilityListener(
+ manager, pathFromString(pathString), cameraId, executor)
+ }
+
+ private fun pathFromString(pathString: String): Path {
+ val spec = pathString.trim()
+ val p: Path
+ try {
+ p = PathParser.createPathFromPathData(spec)
+ } catch (e: Throwable) {
+ throw IllegalArgumentException("Invalid protection path", e)
+ }
+
+ return p
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 0f896c44ae636..1324524c4011a 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -29,6 +29,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import android.annotation.Dimension;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -36,6 +37,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
@@ -105,6 +107,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
private final Handler mMainHandler;
private final TunerService mTunerService;
private DisplayManager.DisplayListener mDisplayListener;
+ private CameraAvailabilityListener mCameraListener;
@VisibleForTesting
protected int mRoundedDefault;
@@ -122,6 +125,26 @@ public class ScreenDecorations extends SystemUI implements Tunable {
private boolean mPendingRotationChange;
private Handler mHandler;
+ private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
+ new CameraAvailabilityListener.CameraTransitionCallback() {
+ @Override
+ public void onApplyCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
+ // Show the extra protection around the front facing camera if necessary
+ for (DisplayCutoutView dcv : mCutoutViews) {
+ dcv.setProtection(protectionPath, bounds);
+ dcv.setShowProtection(true);
+ }
+ }
+
+ @Override
+ public void onHideCameraProtection() {
+ // Go back to the regular anti-aliasing
+ for (DisplayCutoutView dcv : mCutoutViews) {
+ dcv.setShowProtection(false);
+ }
+ }
+ };
+
/**
* Converts a set of {@link Rect}s into a {@link Region}
*
@@ -169,6 +192,8 @@ public class ScreenDecorations extends SystemUI implements Tunable {
mDisplayManager = mContext.getSystemService(DisplayManager.class);
updateRoundedCornerRadii();
setupDecorations();
+ setupCameraListener();
+
mDisplayListener = new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
@@ -443,6 +468,16 @@ public class ScreenDecorations extends SystemUI implements Tunable {
: pos - rotation;
}
+ private void setupCameraListener() {
+ Resources res = mContext.getResources();
+ boolean enabled = res.getBoolean(R.bool.config_enableDisplayCutoutProtection);
+ if (enabled) {
+ mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mHandler::post);
+ mCameraListener.addTransitionCallback(mCameraTransitionCallback);
+ mCameraListener.startListening();
+ }
+ }
+
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -684,6 +719,13 @@ public class ScreenDecorations extends SystemUI implements Tunable {
private final List mBounds = new ArrayList();
private final Rect mBoundingRect = new Rect();
private final Path mBoundingPath = new Path();
+ // Don't initialize these because they are cached elsewhere and may not exist
+ private Rect mProtectionRect;
+ private Path mProtectionPath;
+ private Rect mTotalBounds = new Rect();
+ // Whether or not to show the cutout protection path
+ private boolean mShowProtection = false;
+
private final int[] mLocation = new int[2];
private final ScreenDecorations mDecorations;
private int mColor = Color.BLACK;
@@ -727,7 +769,13 @@ public class ScreenDecorations extends SystemUI implements Tunable {
super.onDraw(canvas);
getLocationOnScreen(mLocation);
canvas.translate(-mLocation[0], -mLocation[1]);
- if (!mBoundingPath.isEmpty()) {
+
+ if (mShowProtection && !mProtectionRect.isEmpty()) {
+ mPaint.setColor(mColor);
+ mPaint.setStyle(Paint.Style.FILL);
+ mPaint.setAntiAlias(true);
+ canvas.drawPath(mProtectionPath, mPaint);
+ } else if (!mBoundingPath.isEmpty()) {
mPaint.setColor(mColor);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
@@ -755,6 +803,22 @@ public class ScreenDecorations extends SystemUI implements Tunable {
update();
}
+ void setProtection(Path protectionPath, Rect pathBounds) {
+ mProtectionPath = protectionPath;
+ mProtectionRect = pathBounds;
+ }
+
+ void setShowProtection(boolean shouldShow) {
+ if (mShowProtection == shouldShow) {
+ return;
+ }
+
+ mShowProtection = shouldShow;
+ updateBoundingPath();
+ requestLayout();
+ invalidate();
+ }
+
private void update() {
if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) {
return;
@@ -794,6 +858,9 @@ public class ScreenDecorations extends SystemUI implements Tunable {
Matrix m = new Matrix();
transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
mBoundingPath.transform(m);
+ if (mProtectionPath != null) {
+ mProtectionPath.transform(m);
+ }
}
private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation,
@@ -855,9 +922,19 @@ public class ScreenDecorations extends SystemUI implements Tunable {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
- setMeasuredDimension(
- resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
- resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
+
+ if (mShowProtection) {
+ // Make sure that our measured height encompases the protection
+ mTotalBounds.union(mBoundingRect);
+ mTotalBounds.union(mProtectionRect);
+ setMeasuredDimension(
+ resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
+ resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0));
+ } else {
+ setMeasuredDimension(
+ resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
+ resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
+ }
}
public static void boundsFromDirection(DisplayCutout displayCutout, int gravity,