Merge "Add SystemUI support for front-facing camera protection" into rvc-dev am: 99608f31b8

Change-Id: Ibeae50bc2be458b8203cf7feceb879e0970bfe23
This commit is contained in:
Automerger Merge Worker
2020-03-05 18:40:50 +00:00
3 changed files with 236 additions and 4 deletions

View File

@@ -506,4 +506,21 @@
<item>@*android:string/status_bar_headset</item>
</string-array>
<!-- 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>

View File

@@ -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
}
}
}

View File

@@ -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<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 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,