summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Evan Laird <evanlaird@google.com> 2020-02-21 14:33:42 -0500
committer Evan Laird <evanlaird@google.com> 2020-02-24 18:44:48 +0000
commit6075dd7f58766902e06941552f27ff5ceacd369f (patch)
treed8e2090c7ccf30b1d78adae59df6157654d5f53b
parent353d26c89790e85273d3922bdf7736209c15bffd (diff)
DO NOT MERGE: Add SystemUI support for front-facing camera protection
Devices with a DisplayCutout configured may want to add some extra area of turned-off pixels around the cutout in order to keep light from leaking into camera hardware. This CL adds two new config values to sysui to enable the configuration of this cutout protection, and listens for CameraManager events telling us that a relevant camera has turned on. Test: manual Bug: 145095085 Change-Id: Ifce67a593247e3a2151d41800ae46a50478e0b7d
-rw-r--r--packages/SystemUI/res/values/config.xml17
-rw-r--r--packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt138
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java83
3 files changed, 234 insertions, 4 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 8811ad57ab5a..6758efac7e2a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -491,4 +491,21 @@
<!-- Respect the drawable/rounded.xml that allow to customize as multiple radius corner path -->
<bool name="config_roundedCornerMultipleRadius">false</bool>
+ <!-- A path similar to frameworks/base/core/res/res/values/config.xml
+ config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
+ cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
+ SystemUI will draw this "protection path" instead of the display cutout path that is normally
+ used for anti-aliasing.
+
+ This path will only be drawn when the front-facing camera turns on, otherwise the main
+ DisplayCutout path will be rendered
+ -->
+ <string translatable="false" name="config_frontBuiltInDisplayCutoutProtection"></string>
+
+ <!-- ID for the camera that needs extra protection -->
+ <string translatable="false" name="config_protectedCameraId"></string>
+
+ <!-- Flag to turn on the rendering of the above path or not -->
+ <bool name="config_enableDisplayCutoutProtection">false</bool>
+
</resources>
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 000000000000..24fa91b9e838
--- /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<CameraTransitionCallback>()
+ 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 84cfd9318932..3a8524a3ee7d 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -29,6 +29,7 @@ import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.Dimension;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.Fragment;
import android.content.BroadcastReceiver;
@@ -37,6 +38,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;
@@ -110,6 +112,7 @@ public class ScreenDecorations extends SystemUI implements Tunable,
private DisplayManager mDisplayManager;
private DisplayManager.DisplayListener mDisplayListener;
+ private CameraAvailabilityListener mCameraListener;
@VisibleForTesting
protected int mRoundedDefault;
@@ -133,6 +136,25 @@ public class ScreenDecorations extends SystemUI implements Tunable,
private boolean mInGesturalMode;
private boolean mIsRoundedCornerMultipleRadius;
+ 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
+ mCutoutTop.setProtection(protectionPath, bounds);
+ mCutoutTop.setShowProtection(true);
+ mCutoutBottom.setProtection(protectionPath, bounds);
+ mCutoutBottom.setShowProtection(true);
+ }
+
+ @Override
+ public void onHideCameraProtection() {
+ // Go back to the regular anti-aliasing
+ mCutoutTop.setShowProtection(false);
+ mCutoutBottom.setShowProtection(false);
+ }
+ };
+
/**
* Converts a set of {@link Rect}s into a {@link Region}
*
@@ -331,6 +353,7 @@ public class ScreenDecorations extends SystemUI implements Tunable,
updateRoundedCornerRadii();
if (hasRoundedCorners() || shouldDrawCutout() || shouldHostHandles()) {
setupDecorations();
+ setupCameraListener();
}
mDisplayListener = new DisplayManager.DisplayListener() {
@@ -446,6 +469,16 @@ public class ScreenDecorations extends SystemUI implements Tunable,
new ValidatingPreDrawListener(mBottomOverlay));
}
+ 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) {
@@ -841,6 +874,13 @@ public class ScreenDecorations extends SystemUI implements Tunable,
private final List<Rect> 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 boolean mInitialStart;
private final Runnable mVisibilityChangedListener;
@@ -887,7 +927,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);
@@ -915,6 +961,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 boolean isStart() {
final boolean flipped = (mRotation == RotationUtils.ROTATION_SEASCAPE
|| mRotation == RotationUtils.ROTATION_UPSIDE_DOWN);
@@ -961,6 +1023,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,
@@ -1018,9 +1083,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,