Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e2fdbeff authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "ScreenDecorations Cutout Provider"

parents 5f27b968 ec7eb0b0
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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" />

+77 −128
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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");
            }
        }
    }

@@ -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");
            }
        }
    }
@@ -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;
    }
@@ -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();
@@ -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) {
@@ -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;
        }
@@ -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);
                }
            }
@@ -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
@@ -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() {
@@ -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,
@@ -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);
@@ -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
@@ -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) {
@@ -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));
@@ -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();
+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"
+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)
        }
    }
}
+59 −10
Original line number Diff line number Diff line
@@ -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() */
@@ -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}"
}

/**
@@ -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