Loading packages/SystemUI/res/values/config.xml +17 −0 Original line number Diff line number Diff line Loading @@ -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> packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt 0 → 100644 +138 −0 Original line number Diff line number Diff line /* * 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 packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +81 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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} * Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -855,10 +922,20 @@ public class ScreenDecorations extends SystemUI implements Tunable { super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } 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, Rect out) { Loading Loading
packages/SystemUI/res/values/config.xml +17 −0 Original line number Diff line number Diff line Loading @@ -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>
packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt 0 → 100644 +138 −0 Original line number Diff line number Diff line /* * 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
packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +81 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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} * Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -855,10 +922,20 @@ public class ScreenDecorations extends SystemUI implements Tunable { super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } 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, Rect out) { Loading