Loading packages/SystemUI/res/values/ids.xml +3 −0 Original line number Diff line number Diff line Loading @@ -132,6 +132,9 @@ <item type="id" name="status_bar_view_state_tag" /> <item type="id" name="display_cutout" /> <item type="id" name="display_cutout_left" /> <item type="id" name="display_cutout_right" /> <item type="id" name="display_cutout_bottom" /> <item type="id" name="row_tag_for_content_view" /> Loading packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +77 −128 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.graphics.common.AlphaInterpretation; import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Build; import android.os.Handler; import android.os.SystemProperties; import android.os.Trace; Loading Loading @@ -72,6 +73,7 @@ import com.android.settingslib.Utils; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.decor.CutoutDecorProviderFactory; import com.android.systemui.decor.DecorProvider; import com.android.systemui.decor.DecorProviderFactory; import com.android.systemui.decor.DecorProviderKt; Loading Loading @@ -139,13 +141,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab protected RoundedCornerResDelegate mRoundedCornerResDelegate; @VisibleForTesting protected DecorProviderFactory mRoundedCornerFactory; private CutoutDecorProviderFactory mCutoutFactory; private int mProviderRefreshToken = 0; @VisibleForTesting protected OverlayWindow[] mOverlays = null; @VisibleForTesting @Nullable DisplayCutoutView[] mCutoutViews; @VisibleForTesting ViewGroup mScreenDecorHwcWindow; @VisibleForTesting ScreenDecorHwcLayer mScreenDecorHwcLayer; Loading Loading @@ -187,18 +187,29 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } if (mCutoutViews == null) { Log.w(TAG, "DisplayCutoutView not initialized onApplyCameraProtection"); return; final int[] ids = { R.id.display_cutout, R.id.display_cutout_left, R.id.display_cutout_right, R.id.display_cutout_bottom }; int setProtectionCnt = 0; for (int id: ids) { final View view = getOverlayView(id); if (!(view instanceof DisplayCutoutView)) { continue; } // Show the extra protection around the front facing camera if necessary for (DisplayCutoutView dcv : mCutoutViews) { // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile if (dcv != null) { ++setProtectionCnt; final DisplayCutoutView dcv = (DisplayCutoutView) view; dcv.setProtection(protectionPath, bounds); dcv.enableShowProtection(true); } if (setProtectionCnt == 0) { if (Build.isDebuggable()) { throw new RuntimeException("CutoutView not initialized showCameraProtection"); } else { Log.e(TAG, "CutoutView not initialized showCameraProtection"); } } } Loading @@ -219,15 +230,26 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } if (mCutoutViews == null) { Log.w(TAG, "DisplayCutoutView not initialized onHideCameraProtection"); return; final int[] ids = { R.id.display_cutout, R.id.display_cutout_left, R.id.display_cutout_right, R.id.display_cutout_bottom }; int setProtectionCnt = 0; for (int id: ids) { final View view = getOverlayView(id); if (!(view instanceof DisplayCutoutView)) { continue; } ++setProtectionCnt; ((DisplayCutoutView) view).enableShowProtection(false); } // Go back to the regular anti-aliasing for (DisplayCutoutView dcv : mCutoutViews) { // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile if (dcv != null) { dcv.enableShowProtection(false); if (setProtectionCnt == 0) { if (Build.isDebuggable()) { throw new RuntimeException("CutoutView not initialized hideCameraProtection"); } else { Log.e(TAG, "CutoutView not initialized hideCameraProtection"); } } } Loading Loading @@ -335,6 +357,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab decorProviders.addAll(mFaceScanningFactory.getProviders()); if (!hasHwLayer) { decorProviders.addAll(mRoundedCornerFactory.getProviders()); decorProviders.addAll(mCutoutFactory.getProviders()); } return decorProviders; } Loading Loading @@ -379,6 +402,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( getPhysicalPixelDisplaySizeRatio()); mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate); mCutoutFactory = getCutoutFactory(); mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport(); updateHwLayerRoundedCornerDrawable(); setupDecorations(); Loading Loading @@ -483,18 +507,13 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (needToUpdateProviderViews) { updateOverlayProviderViews(null); } else { updateOverlayProviderViews(new Integer[] { mFaceScanningViewId }); } if (mCutoutViews != null) { final int size = mCutoutViews.length; for (int i = 0; i < size; i++) { final DisplayCutoutView cutoutView = mCutoutViews[i]; if (cutoutView == null) { continue; } cutoutView.onDisplayChanged(newUniqueId); } updateOverlayProviderViews(new Integer[] { mFaceScanningViewId, R.id.display_cutout, R.id.display_cutout_left, R.id.display_cutout_right, R.id.display_cutout_bottom, }); } if (mScreenDecorHwcLayer != null) { Loading @@ -507,8 +526,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab updateConfiguration(); } @VisibleForTesting @Nullable private View getOverlayView(@IdRes int id) { View getOverlayView(@IdRes int id) { if (mOverlays == null) { return null; } Loading Loading @@ -565,18 +585,18 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab removeHwcOverlay(); } final DisplayCutout cutout = getCutout(); boolean[] hasCreatedOverlay = new boolean[BOUNDS_POSITION_LENGTH]; final boolean shouldOptimizeVisibility = shouldOptimizeVisibility(); for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (shouldShowSwLayerCutout(i, cutout) || shouldShowSwLayerFaceScan(i, cutout) || shouldShowSwLayerRoundedCorner(i, cutout) || shouldShowSwLayerPrivacyDot(i, cutout)) { Integer bound; while ((bound = DecorProviderKt.getProperBound(decorProviders)) != null) { hasCreatedOverlay[bound] = true; Pair<List<DecorProvider>, List<DecorProvider>> pair = DecorProviderKt.partitionAlignedBound(decorProviders, i); DecorProviderKt.partitionAlignedBound(decorProviders, bound); decorProviders = pair.getSecond(); createOverlay(i, pair.getFirst(), shouldOptimizeVisibility); } else { createOverlay(bound, pair.getFirst(), shouldOptimizeVisibility); } for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (!hasCreatedOverlay[i]) { removeOverlay(i); } } Loading Loading @@ -639,9 +659,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } @VisibleForTesting DisplayCutout getCutout() { return mContext.getDisplay().getCutout(); // For unit test to override protected CutoutDecorProviderFactory getCutoutFactory() { return new CutoutDecorProviderFactory(mContext.getResources(), mContext.getDisplay()); } @VisibleForTesting Loading Loading @@ -731,16 +752,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab overlayView.setAlpha(0); overlayView.setForceDarkAllowed(false); // Only show cutout in mOverlays when hwc doesn't support screen decoration if (mHwcScreenDecorationSupport == null) { if (mCutoutViews == null) { mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH]; } mCutoutViews[pos] = new DisplayCutoutView(mContext, pos); overlayView.addView(mCutoutViews[pos]); mCutoutViews[pos].updateRotation(mRotation); } mWindowManager.addView(overlayView, getWindowLayoutParams(pos)); overlayView.addOnLayoutChangeListener(new OnLayoutChangeListener() { Loading Loading @@ -947,27 +958,12 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mTintColor = Color.RED; } if (mOverlays == null) { return; } for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (mOverlays[i] == null) { continue; } final ViewGroup overlayView = mOverlays[i].getRootView(); final int size = overlayView.getChildCount(); View child; for (int j = 0; j < size; j++) { child = overlayView.getChildAt(j); if (child instanceof DisplayCutoutView && child.getId() == R.id.display_cutout) { ((DisplayCutoutView) child).setColor(mTintColor); } } } updateOverlayProviderViews(new Integer[] { mFaceScanningViewId, R.id.display_cutout, R.id.display_cutout_left, R.id.display_cutout_right, R.id.display_cutout_bottom, R.id.rounded_corner_top_left, R.id.rounded_corner_top_right, R.id.rounded_corner_bottom_left, Loading Loading @@ -1092,15 +1088,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab updateHwLayerRoundedCornerDrawable(); } updateLayoutParams(); // update cutout view rotation if (mCutoutViews != null) { for (final DisplayCutoutView cutoutView: mCutoutViews) { if (cutoutView == null) { continue; } cutoutView.updateRotation(mRotation); } } // update all provider views inside overlay updateOverlayProviderViews(null); Loading @@ -1119,46 +1106,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return mRoundedCornerFactory.getHasProviders(); } private boolean isDefaultShownOverlayPos(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { // for cutout is null or cutout with only waterfall. final boolean emptyBoundsOrWaterfall = cutout == null || cutout.isBoundsEmpty(); // Shows rounded corner on left and right overlays only when there is no top or bottom // cutout. final int rotatedTop = getBoundPositionFromRotation(BOUNDS_POSITION_TOP, mRotation); final int rotatedBottom = getBoundPositionFromRotation(BOUNDS_POSITION_BOTTOM, mRotation); if (emptyBoundsOrWaterfall || !cutout.getBoundingRectsAll()[rotatedTop].isEmpty() || !cutout.getBoundingRectsAll()[rotatedBottom].isEmpty()) { return pos == BOUNDS_POSITION_TOP || pos == BOUNDS_POSITION_BOTTOM; } else { return pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_RIGHT; } } private boolean shouldShowSwLayerRoundedCorner(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout) && mHwcScreenDecorationSupport == null; } private boolean shouldShowSwLayerPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout); } private boolean shouldShowSwLayerFaceScan(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { return mFaceScanningFactory.getHasProviders() && isDefaultShownOverlayPos(pos, cutout); } private boolean shouldShowSwLayerCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll(); final int rotatedPos = getBoundPositionFromRotation(pos, mRotation); return (bounds != null && !bounds[rotatedPos].isEmpty() && mHwcScreenDecorationSupport == null); } private boolean shouldOptimizeVisibility() { return (isPrivacyDotEnabled() || mFaceScanningFactory.getHasProviders()) && (mHwcScreenDecorationSupport != null Loading @@ -1167,7 +1114,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private boolean shouldDrawCutout() { return shouldDrawCutout(mContext); return mCutoutFactory.getHasProviders(); } static boolean shouldDrawCutout(Context context) { Loading Loading @@ -1283,7 +1230,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab paint.setColor(mColor); paint.setStyle(Paint.Style.FILL); setId(R.id.display_cutout); if (DEBUG) { getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG, getWindowTitleByPos(pos) + " drawn in rot " + mRotation)); Loading @@ -1291,6 +1237,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } public void setColor(int color) { if (color == mColor) { return; } mColor = color; paint.setColor(mColor); invalidate(); Loading packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt 0 → 100644 +60 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.decor import android.content.res.Resources import android.util.Log import android.view.Display import android.view.DisplayCutout import android.view.DisplayInfo class CutoutDecorProviderFactory constructor( private val res: Resources, private val display: Display?, ) : DecorProviderFactory() { val displayInfo = DisplayInfo() override val hasProviders: Boolean get() { display?.getDisplayInfo(displayInfo) ?: run { Log.w(TAG, "display is null, can't update displayInfo") } return DisplayCutout.getFillBuiltInDisplayCutout(res, displayInfo.uniqueId) } override val providers: List<DecorProvider> get() { if (!hasProviders) { return emptyList() } return ArrayList<DecorProvider>().also { list -> // We need to update displayInfo before using it, but it has already updated during // accessing hasProviders field displayInfo.displayCutout?.getBoundBaseOnCurrentRotation()?.let { bounds -> for (bound in bounds) { list.add( CutoutDecorProviderImpl(bound.baseOnRotation0(displayInfo.rotation)) ) } } } } } private const val TAG = "CutoutDecorProviderFactory" packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt 0 → 100644 +65 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.decor import android.content.Context import android.view.DisplayCutout import android.view.Surface import android.view.View import android.view.ViewGroup import com.android.systemui.R import com.android.systemui.ScreenDecorations.DisplayCutoutView class CutoutDecorProviderImpl( @DisplayCutout.BoundsPosition override val alignedBound: Int ) : BoundDecorProvider() { override val viewId: Int = when (alignedBound) { DisplayCutout.BOUNDS_POSITION_TOP -> R.id.display_cutout DisplayCutout.BOUNDS_POSITION_LEFT -> R.id.display_cutout_left DisplayCutout.BOUNDS_POSITION_RIGHT -> R.id.display_cutout_right else -> R.id.display_cutout_bottom } override fun inflateView( context: Context, parent: ViewGroup, @Surface.Rotation rotation: Int, tintColor: Int ): View { return DisplayCutoutView(context, alignedBound).also { view -> view.id = viewId view.setColor(tintColor) parent.addView(view) view.updateRotation(rotation) } } override fun onReloadResAndMeasure( view: View, reloadToken: Int, @Surface.Rotation rotation: Int, tintColor: Int, displayUniqueId: String? ) { (view as? DisplayCutoutView)?.let { cutoutView -> cutoutView.setColor(tintColor) cutoutView.updateRotation(rotation) cutoutView.onDisplayChanged(displayUniqueId) } } } packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt +59 −10 Original line number Diff line number Diff line Loading @@ -32,7 +32,7 @@ abstract class DecorProvider { abstract val viewId: Int /** The number of total aligned bounds */ val numOfAlignedEdge: Int val numOfAlignedBound: Int get() = alignedBounds.size /** The aligned bounds for the view which is created through inflateView() */ Loading @@ -57,16 +57,8 @@ abstract class DecorProvider { @Surface.Rotation rotation: Int, tintColor: Int ): View } /** * Split list to 2 list, and return it back as Pair<>. The providers on the first list contains this * alignedBound element. The providers on the second list do not contain this alignedBound element */ fun List<DecorProvider>.partitionAlignedBound( @DisplayCutout.BoundsPosition alignedBound: Int ): Pair<List<DecorProvider>, List<DecorProvider>> { return partition { it.alignedBounds.contains(alignedBound) } override fun toString() = "${javaClass.simpleName}{alignedBounds=$alignedBounds}" } /** Loading Loading @@ -94,3 +86,60 @@ abstract class BoundDecorProvider : DecorProvider() { listOf(alignedBound) } } /** * Split list to 2 list, and return it back as Pair<>. The providers on the first list contains this * alignedBound element. The providers on the second list do not contain this alignedBound element */ fun List<DecorProvider>.partitionAlignedBound( @DisplayCutout.BoundsPosition alignedBound: Int ): Pair<List<DecorProvider>, List<DecorProvider>> { return partition { it.alignedBounds.contains(alignedBound) } } /** * Get the proper bound from DecorProvider list * Time complexity: O(N), N is the number of providers * * Choose order * 1. Return null if list is empty * 2. If list contains BoundDecorProvider, return its alignedBound[0] because it is a must-have * bound * 3. Return the bound with most DecorProviders */ fun List<DecorProvider>.getProperBound(): Int? { // Return null if list is empty if (isEmpty()) { return null } // Choose alignedBounds[0] of BoundDecorProvider if any val singleBoundProvider = firstOrNull { it.numOfAlignedBound == 1 } if (singleBoundProvider != null) { return singleBoundProvider.alignedBounds[0] } // Return the bound with most DecorProviders val boundCount = intArrayOf(0, 0, 0, 0) for (provider in this) { for (bound in provider.alignedBounds) { boundCount[bound]++ } } var maxCount = 0 var maxCountBound: Int? = null val bounds = arrayOf( // Put top and bottom at first to get the highest priority to be chosen DisplayCutout.BOUNDS_POSITION_TOP, DisplayCutout.BOUNDS_POSITION_BOTTOM, DisplayCutout.BOUNDS_POSITION_LEFT, DisplayCutout.BOUNDS_POSITION_RIGHT ) for (bound in bounds) { if (boundCount[bound] > maxCount) { maxCountBound = bound maxCount = boundCount[bound] } } return maxCountBound } Loading
packages/SystemUI/res/values/ids.xml +3 −0 Original line number Diff line number Diff line Loading @@ -132,6 +132,9 @@ <item type="id" name="status_bar_view_state_tag" /> <item type="id" name="display_cutout" /> <item type="id" name="display_cutout_left" /> <item type="id" name="display_cutout_right" /> <item type="id" name="display_cutout_bottom" /> <item type="id" name="row_tag_for_content_view" /> Loading
packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +77 −128 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.graphics.common.AlphaInterpretation; import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Build; import android.os.Handler; import android.os.SystemProperties; import android.os.Trace; Loading Loading @@ -72,6 +73,7 @@ import com.android.settingslib.Utils; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.decor.CutoutDecorProviderFactory; import com.android.systemui.decor.DecorProvider; import com.android.systemui.decor.DecorProviderFactory; import com.android.systemui.decor.DecorProviderKt; Loading Loading @@ -139,13 +141,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab protected RoundedCornerResDelegate mRoundedCornerResDelegate; @VisibleForTesting protected DecorProviderFactory mRoundedCornerFactory; private CutoutDecorProviderFactory mCutoutFactory; private int mProviderRefreshToken = 0; @VisibleForTesting protected OverlayWindow[] mOverlays = null; @VisibleForTesting @Nullable DisplayCutoutView[] mCutoutViews; @VisibleForTesting ViewGroup mScreenDecorHwcWindow; @VisibleForTesting ScreenDecorHwcLayer mScreenDecorHwcLayer; Loading Loading @@ -187,18 +187,29 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } if (mCutoutViews == null) { Log.w(TAG, "DisplayCutoutView not initialized onApplyCameraProtection"); return; final int[] ids = { R.id.display_cutout, R.id.display_cutout_left, R.id.display_cutout_right, R.id.display_cutout_bottom }; int setProtectionCnt = 0; for (int id: ids) { final View view = getOverlayView(id); if (!(view instanceof DisplayCutoutView)) { continue; } // Show the extra protection around the front facing camera if necessary for (DisplayCutoutView dcv : mCutoutViews) { // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile if (dcv != null) { ++setProtectionCnt; final DisplayCutoutView dcv = (DisplayCutoutView) view; dcv.setProtection(protectionPath, bounds); dcv.enableShowProtection(true); } if (setProtectionCnt == 0) { if (Build.isDebuggable()) { throw new RuntimeException("CutoutView not initialized showCameraProtection"); } else { Log.e(TAG, "CutoutView not initialized showCameraProtection"); } } } Loading @@ -219,15 +230,26 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } if (mCutoutViews == null) { Log.w(TAG, "DisplayCutoutView not initialized onHideCameraProtection"); return; final int[] ids = { R.id.display_cutout, R.id.display_cutout_left, R.id.display_cutout_right, R.id.display_cutout_bottom }; int setProtectionCnt = 0; for (int id: ids) { final View view = getOverlayView(id); if (!(view instanceof DisplayCutoutView)) { continue; } ++setProtectionCnt; ((DisplayCutoutView) view).enableShowProtection(false); } // Go back to the regular anti-aliasing for (DisplayCutoutView dcv : mCutoutViews) { // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile if (dcv != null) { dcv.enableShowProtection(false); if (setProtectionCnt == 0) { if (Build.isDebuggable()) { throw new RuntimeException("CutoutView not initialized hideCameraProtection"); } else { Log.e(TAG, "CutoutView not initialized hideCameraProtection"); } } } Loading Loading @@ -335,6 +357,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab decorProviders.addAll(mFaceScanningFactory.getProviders()); if (!hasHwLayer) { decorProviders.addAll(mRoundedCornerFactory.getProviders()); decorProviders.addAll(mCutoutFactory.getProviders()); } return decorProviders; } Loading Loading @@ -379,6 +402,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( getPhysicalPixelDisplaySizeRatio()); mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate); mCutoutFactory = getCutoutFactory(); mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport(); updateHwLayerRoundedCornerDrawable(); setupDecorations(); Loading Loading @@ -483,18 +507,13 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (needToUpdateProviderViews) { updateOverlayProviderViews(null); } else { updateOverlayProviderViews(new Integer[] { mFaceScanningViewId }); } if (mCutoutViews != null) { final int size = mCutoutViews.length; for (int i = 0; i < size; i++) { final DisplayCutoutView cutoutView = mCutoutViews[i]; if (cutoutView == null) { continue; } cutoutView.onDisplayChanged(newUniqueId); } updateOverlayProviderViews(new Integer[] { mFaceScanningViewId, R.id.display_cutout, R.id.display_cutout_left, R.id.display_cutout_right, R.id.display_cutout_bottom, }); } if (mScreenDecorHwcLayer != null) { Loading @@ -507,8 +526,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab updateConfiguration(); } @VisibleForTesting @Nullable private View getOverlayView(@IdRes int id) { View getOverlayView(@IdRes int id) { if (mOverlays == null) { return null; } Loading Loading @@ -565,18 +585,18 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab removeHwcOverlay(); } final DisplayCutout cutout = getCutout(); boolean[] hasCreatedOverlay = new boolean[BOUNDS_POSITION_LENGTH]; final boolean shouldOptimizeVisibility = shouldOptimizeVisibility(); for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (shouldShowSwLayerCutout(i, cutout) || shouldShowSwLayerFaceScan(i, cutout) || shouldShowSwLayerRoundedCorner(i, cutout) || shouldShowSwLayerPrivacyDot(i, cutout)) { Integer bound; while ((bound = DecorProviderKt.getProperBound(decorProviders)) != null) { hasCreatedOverlay[bound] = true; Pair<List<DecorProvider>, List<DecorProvider>> pair = DecorProviderKt.partitionAlignedBound(decorProviders, i); DecorProviderKt.partitionAlignedBound(decorProviders, bound); decorProviders = pair.getSecond(); createOverlay(i, pair.getFirst(), shouldOptimizeVisibility); } else { createOverlay(bound, pair.getFirst(), shouldOptimizeVisibility); } for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (!hasCreatedOverlay[i]) { removeOverlay(i); } } Loading Loading @@ -639,9 +659,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } @VisibleForTesting DisplayCutout getCutout() { return mContext.getDisplay().getCutout(); // For unit test to override protected CutoutDecorProviderFactory getCutoutFactory() { return new CutoutDecorProviderFactory(mContext.getResources(), mContext.getDisplay()); } @VisibleForTesting Loading Loading @@ -731,16 +752,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab overlayView.setAlpha(0); overlayView.setForceDarkAllowed(false); // Only show cutout in mOverlays when hwc doesn't support screen decoration if (mHwcScreenDecorationSupport == null) { if (mCutoutViews == null) { mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH]; } mCutoutViews[pos] = new DisplayCutoutView(mContext, pos); overlayView.addView(mCutoutViews[pos]); mCutoutViews[pos].updateRotation(mRotation); } mWindowManager.addView(overlayView, getWindowLayoutParams(pos)); overlayView.addOnLayoutChangeListener(new OnLayoutChangeListener() { Loading Loading @@ -947,27 +958,12 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mTintColor = Color.RED; } if (mOverlays == null) { return; } for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (mOverlays[i] == null) { continue; } final ViewGroup overlayView = mOverlays[i].getRootView(); final int size = overlayView.getChildCount(); View child; for (int j = 0; j < size; j++) { child = overlayView.getChildAt(j); if (child instanceof DisplayCutoutView && child.getId() == R.id.display_cutout) { ((DisplayCutoutView) child).setColor(mTintColor); } } } updateOverlayProviderViews(new Integer[] { mFaceScanningViewId, R.id.display_cutout, R.id.display_cutout_left, R.id.display_cutout_right, R.id.display_cutout_bottom, R.id.rounded_corner_top_left, R.id.rounded_corner_top_right, R.id.rounded_corner_bottom_left, Loading Loading @@ -1092,15 +1088,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab updateHwLayerRoundedCornerDrawable(); } updateLayoutParams(); // update cutout view rotation if (mCutoutViews != null) { for (final DisplayCutoutView cutoutView: mCutoutViews) { if (cutoutView == null) { continue; } cutoutView.updateRotation(mRotation); } } // update all provider views inside overlay updateOverlayProviderViews(null); Loading @@ -1119,46 +1106,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return mRoundedCornerFactory.getHasProviders(); } private boolean isDefaultShownOverlayPos(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { // for cutout is null or cutout with only waterfall. final boolean emptyBoundsOrWaterfall = cutout == null || cutout.isBoundsEmpty(); // Shows rounded corner on left and right overlays only when there is no top or bottom // cutout. final int rotatedTop = getBoundPositionFromRotation(BOUNDS_POSITION_TOP, mRotation); final int rotatedBottom = getBoundPositionFromRotation(BOUNDS_POSITION_BOTTOM, mRotation); if (emptyBoundsOrWaterfall || !cutout.getBoundingRectsAll()[rotatedTop].isEmpty() || !cutout.getBoundingRectsAll()[rotatedBottom].isEmpty()) { return pos == BOUNDS_POSITION_TOP || pos == BOUNDS_POSITION_BOTTOM; } else { return pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_RIGHT; } } private boolean shouldShowSwLayerRoundedCorner(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout) && mHwcScreenDecorationSupport == null; } private boolean shouldShowSwLayerPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout); } private boolean shouldShowSwLayerFaceScan(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { return mFaceScanningFactory.getHasProviders() && isDefaultShownOverlayPos(pos, cutout); } private boolean shouldShowSwLayerCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll(); final int rotatedPos = getBoundPositionFromRotation(pos, mRotation); return (bounds != null && !bounds[rotatedPos].isEmpty() && mHwcScreenDecorationSupport == null); } private boolean shouldOptimizeVisibility() { return (isPrivacyDotEnabled() || mFaceScanningFactory.getHasProviders()) && (mHwcScreenDecorationSupport != null Loading @@ -1167,7 +1114,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private boolean shouldDrawCutout() { return shouldDrawCutout(mContext); return mCutoutFactory.getHasProviders(); } static boolean shouldDrawCutout(Context context) { Loading Loading @@ -1283,7 +1230,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab paint.setColor(mColor); paint.setStyle(Paint.Style.FILL); setId(R.id.display_cutout); if (DEBUG) { getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG, getWindowTitleByPos(pos) + " drawn in rot " + mRotation)); Loading @@ -1291,6 +1237,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } public void setColor(int color) { if (color == mColor) { return; } mColor = color; paint.setColor(mColor); invalidate(); Loading
packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt 0 → 100644 +60 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.decor import android.content.res.Resources import android.util.Log import android.view.Display import android.view.DisplayCutout import android.view.DisplayInfo class CutoutDecorProviderFactory constructor( private val res: Resources, private val display: Display?, ) : DecorProviderFactory() { val displayInfo = DisplayInfo() override val hasProviders: Boolean get() { display?.getDisplayInfo(displayInfo) ?: run { Log.w(TAG, "display is null, can't update displayInfo") } return DisplayCutout.getFillBuiltInDisplayCutout(res, displayInfo.uniqueId) } override val providers: List<DecorProvider> get() { if (!hasProviders) { return emptyList() } return ArrayList<DecorProvider>().also { list -> // We need to update displayInfo before using it, but it has already updated during // accessing hasProviders field displayInfo.displayCutout?.getBoundBaseOnCurrentRotation()?.let { bounds -> for (bound in bounds) { list.add( CutoutDecorProviderImpl(bound.baseOnRotation0(displayInfo.rotation)) ) } } } } } private const val TAG = "CutoutDecorProviderFactory"
packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt 0 → 100644 +65 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.decor import android.content.Context import android.view.DisplayCutout import android.view.Surface import android.view.View import android.view.ViewGroup import com.android.systemui.R import com.android.systemui.ScreenDecorations.DisplayCutoutView class CutoutDecorProviderImpl( @DisplayCutout.BoundsPosition override val alignedBound: Int ) : BoundDecorProvider() { override val viewId: Int = when (alignedBound) { DisplayCutout.BOUNDS_POSITION_TOP -> R.id.display_cutout DisplayCutout.BOUNDS_POSITION_LEFT -> R.id.display_cutout_left DisplayCutout.BOUNDS_POSITION_RIGHT -> R.id.display_cutout_right else -> R.id.display_cutout_bottom } override fun inflateView( context: Context, parent: ViewGroup, @Surface.Rotation rotation: Int, tintColor: Int ): View { return DisplayCutoutView(context, alignedBound).also { view -> view.id = viewId view.setColor(tintColor) parent.addView(view) view.updateRotation(rotation) } } override fun onReloadResAndMeasure( view: View, reloadToken: Int, @Surface.Rotation rotation: Int, tintColor: Int, displayUniqueId: String? ) { (view as? DisplayCutoutView)?.let { cutoutView -> cutoutView.setColor(tintColor) cutoutView.updateRotation(rotation) cutoutView.onDisplayChanged(displayUniqueId) } } }
packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt +59 −10 Original line number Diff line number Diff line Loading @@ -32,7 +32,7 @@ abstract class DecorProvider { abstract val viewId: Int /** The number of total aligned bounds */ val numOfAlignedEdge: Int val numOfAlignedBound: Int get() = alignedBounds.size /** The aligned bounds for the view which is created through inflateView() */ Loading @@ -57,16 +57,8 @@ abstract class DecorProvider { @Surface.Rotation rotation: Int, tintColor: Int ): View } /** * Split list to 2 list, and return it back as Pair<>. The providers on the first list contains this * alignedBound element. The providers on the second list do not contain this alignedBound element */ fun List<DecorProvider>.partitionAlignedBound( @DisplayCutout.BoundsPosition alignedBound: Int ): Pair<List<DecorProvider>, List<DecorProvider>> { return partition { it.alignedBounds.contains(alignedBound) } override fun toString() = "${javaClass.simpleName}{alignedBounds=$alignedBounds}" } /** Loading Loading @@ -94,3 +86,60 @@ abstract class BoundDecorProvider : DecorProvider() { listOf(alignedBound) } } /** * Split list to 2 list, and return it back as Pair<>. The providers on the first list contains this * alignedBound element. The providers on the second list do not contain this alignedBound element */ fun List<DecorProvider>.partitionAlignedBound( @DisplayCutout.BoundsPosition alignedBound: Int ): Pair<List<DecorProvider>, List<DecorProvider>> { return partition { it.alignedBounds.contains(alignedBound) } } /** * Get the proper bound from DecorProvider list * Time complexity: O(N), N is the number of providers * * Choose order * 1. Return null if list is empty * 2. If list contains BoundDecorProvider, return its alignedBound[0] because it is a must-have * bound * 3. Return the bound with most DecorProviders */ fun List<DecorProvider>.getProperBound(): Int? { // Return null if list is empty if (isEmpty()) { return null } // Choose alignedBounds[0] of BoundDecorProvider if any val singleBoundProvider = firstOrNull { it.numOfAlignedBound == 1 } if (singleBoundProvider != null) { return singleBoundProvider.alignedBounds[0] } // Return the bound with most DecorProviders val boundCount = intArrayOf(0, 0, 0, 0) for (provider in this) { for (bound in provider.alignedBounds) { boundCount[bound]++ } } var maxCount = 0 var maxCountBound: Int? = null val bounds = arrayOf( // Put top and bottom at first to get the highest priority to be chosen DisplayCutout.BOUNDS_POSITION_TOP, DisplayCutout.BOUNDS_POSITION_BOTTOM, DisplayCutout.BOUNDS_POSITION_LEFT, DisplayCutout.BOUNDS_POSITION_RIGHT ) for (bound in bounds) { if (boundCount[bound] > maxCount) { maxCountBound = bound maxCount = boundCount[bound] } } return maxCountBound }