Merge "Add SystemUI support for front-facing camera protection" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
99608f31b8
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user