diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 8811ad57ab5a6..6758efac7e2a9 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -491,4 +491,21 @@ false + + + + + + + + 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 84cfd93189325..3a8524a3ee7da 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 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,