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

Commit 53450fbe authored by Christian Göllner's avatar Christian Göllner Committed by Christian Go??llner
Browse files

Create LetterboxAppearanceCalculator for SysUI

This class is responsible for calculating appearance and appearance
regions for the status bar, whenever apps are letterboxed.

Change-Id: I3adfa99f4908ae316ae9309e19c0db8a4348f27a
Merged-In: I3adfa99f4908ae316ae9309e19c0db8a4348f27a
Test: LetterboxAppearanceCalculatorTest.kt
Test: Manually
Fixes: 238588538
parent 09f7ed7e
Loading
Loading
Loading
Loading
+17 −1
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.window.StatusBarWindowController
import java.lang.IllegalStateException
import javax.inject.Inject
@@ -34,7 +35,8 @@ import javax.inject.Inject
 */
@CentralSurfacesScope
class StatusBarInitializer @Inject constructor(
    private val windowController: StatusBarWindowController
    private val windowController: StatusBarWindowController,
    private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
) {

    var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
@@ -56,6 +58,9 @@ class StatusBarInitializer @Inject constructor(
                            statusBarFragmentComponent.phoneStatusBarViewController,
                            statusBarFragmentComponent.phoneStatusBarTransitions
                        )
                        creationListeners.forEach { listener ->
                            listener.onStatusBarViewInitialized(statusBarFragmentComponent)
                        }
                    }

                    override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
@@ -69,6 +74,17 @@ class StatusBarInitializer @Inject constructor(
                .commit()
    }

    interface OnStatusBarViewInitializedListener {

        /**
         * The status bar view has been initialized.
         *
         * @param component Dagger component that is created when the status bar view is created.
         * Can be used to retrieve dependencies from that scope, including the status bar root view.
         */
        fun onStatusBarViewInitialized(component: StatusBarFragmentComponent)
    }

    interface OnStatusBarViewUpdatedListener {
        fun onStatusBarViewUpdated(
            statusBarView: PhoneStatusBarView,
+227 −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.statusbar.phone

import android.annotation.ColorInt
import android.graphics.Color
import android.graphics.Rect
import android.view.InsetsFlags
import android.view.ViewDebug
import android.view.WindowInsetsController
import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
import android.view.WindowInsetsController.Appearance
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.util.ContrastColorUtil
import com.android.internal.view.AppearanceRegion
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import java.io.PrintWriter
import java.util.Arrays
import javax.inject.Inject

class LetterboxAppearance(
    @Appearance val appearance: Int,
    val appearanceRegions: Array<AppearanceRegion>
)

/**
 * Responsible for calculating the [Appearance] and [AppearanceRegion] for the status bar when apps
 * are letterboxed.
 */
@CentralSurfacesScope
class LetterboxAppearanceCalculator
@Inject
constructor(
    private val lightBarController: LightBarController,
    private val dumpManager: DumpManager,
) : OnStatusBarViewInitializedListener, CentralSurfacesComponent.Startable {

    private var statusBarBoundsProvider: StatusBarBoundsProvider? = null

    override fun start() {
        dumpManager.registerDumpable(javaClass.simpleName) { printWriter, _ -> dump(printWriter) }
    }

    override fun stop() {
        dumpManager.unregisterDumpable(javaClass.simpleName)
    }

    private var lastAppearance: Int? = null
    private var lastAppearanceRegions: Array<AppearanceRegion>? = null
    private var lastLetterboxes: Array<LetterboxDetails>? = null
    private var lastLetterboxAppearance: LetterboxAppearance? = null

    fun getLetterboxAppearance(
        @Appearance originalAppearance: Int,
        originalAppearanceRegions: Array<AppearanceRegion>,
        letterboxes: Array<LetterboxDetails>
    ): LetterboxAppearance {
        lastAppearance = originalAppearance
        lastAppearanceRegions = originalAppearanceRegions
        lastLetterboxes = letterboxes
        return getLetterboxAppearanceInternal(
                letterboxes, originalAppearance, originalAppearanceRegions)
            .also { lastLetterboxAppearance = it }
    }

    private fun getLetterboxAppearanceInternal(
        letterboxes: Array<LetterboxDetails>,
        originalAppearance: Int,
        originalAppearanceRegions: Array<AppearanceRegion>
    ): LetterboxAppearance {
        if (isScrimNeeded(letterboxes)) {
            return originalAppearanceWithScrim(originalAppearance, originalAppearanceRegions)
        }
        val appearance = appearanceWithoutScrim(originalAppearance)
        val appearanceRegions = getAppearanceRegions(originalAppearanceRegions, letterboxes)
        return LetterboxAppearance(appearance, appearanceRegions.toTypedArray())
    }

    private fun isScrimNeeded(letterboxes: Array<LetterboxDetails>): Boolean {
        if (isOuterLetterboxMultiColored()) {
            return true
        }
        return letterboxes.any { letterbox ->
            letterbox.letterboxInnerBounds.overlapsWith(getStartSideIconBounds()) ||
                letterbox.letterboxInnerBounds.overlapsWith(getEndSideIconsBounds())
        }
    }

    private fun getAppearanceRegions(
        originalAppearanceRegions: Array<AppearanceRegion>,
        letterboxes: Array<LetterboxDetails>
    ): List<AppearanceRegion> {
        return sanitizeAppearanceRegions(originalAppearanceRegions, letterboxes) +
            getAllOuterAppearanceRegions(letterboxes)
    }

    private fun sanitizeAppearanceRegions(
        originalAppearanceRegions: Array<AppearanceRegion>,
        letterboxes: Array<LetterboxDetails>
    ): List<AppearanceRegion> =
        originalAppearanceRegions.map { appearanceRegion ->
            val matchingLetterbox =
                letterboxes.find { it.letterboxFullBounds == appearanceRegion.bounds }
            if (matchingLetterbox == null) {
                appearanceRegion
            } else {
                // When WindowManager sends appearance regions for an app, it sends them for the
                // full bounds of its window.
                // Here we want the bounds to be only for the inner bounds of the letterboxed app.
                AppearanceRegion(
                    appearanceRegion.appearance, matchingLetterbox.letterboxInnerBounds)
            }
        }

    private fun originalAppearanceWithScrim(
        @Appearance originalAppearance: Int,
        originalAppearanceRegions: Array<AppearanceRegion>
    ): LetterboxAppearance {
        return LetterboxAppearance(
            originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
            originalAppearanceRegions)
    }

    @Appearance
    private fun appearanceWithoutScrim(@Appearance originalAppearance: Int): Int =
        originalAppearance and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS.inv()

    private fun getAllOuterAppearanceRegions(
        letterboxes: Array<LetterboxDetails>
    ): List<AppearanceRegion> = letterboxes.map(this::getOuterAppearanceRegions).flatten()

    private fun getOuterAppearanceRegions(
        letterboxDetails: LetterboxDetails
    ): List<AppearanceRegion> {
        @Appearance val outerAppearance = getOuterAppearance()
        return getVisibleOuterBounds(letterboxDetails).map { bounds ->
            AppearanceRegion(outerAppearance, bounds)
        }
    }

    private fun getVisibleOuterBounds(letterboxDetails: LetterboxDetails): List<Rect> {
        val inner = letterboxDetails.letterboxInnerBounds
        val outer = letterboxDetails.letterboxFullBounds
        val top = Rect(outer.left, outer.top, outer.right, inner.top)
        val left = Rect(outer.left, outer.top, inner.left, outer.bottom)
        val right = Rect(inner.right, outer.top, outer.right, outer.bottom)
        val bottom = Rect(outer.left, inner.bottom, outer.right, outer.bottom)
        return listOf(left, top, right, bottom).filter { !it.isEmpty }
    }

    @Appearance
    private fun getOuterAppearance(): Int {
        val backgroundColor = outerLetterboxBackgroundColor()
        val darkAppearanceContrast =
            ContrastColorUtil.calculateContrast(
                lightBarController.darkAppearanceIconColor, backgroundColor)
        val lightAppearanceContrast =
            ContrastColorUtil.calculateContrast(
                lightBarController.lightAppearanceIconColor, backgroundColor)
        return if (lightAppearanceContrast > darkAppearanceContrast) {
            WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
        } else {
            0 // APPEARANCE_DEFAULT
        }
    }

    @ColorInt
    private fun outerLetterboxBackgroundColor(): Int {
        // TODO(b/238607453): retrieve this information from WindowManager.
        return Color.BLACK
    }

    private fun isOuterLetterboxMultiColored(): Boolean {
        // TODO(b/238607453): retrieve this information from WindowManager.
        return false
    }

    private fun getEndSideIconsBounds(): Rect {
        return statusBarBoundsProvider?.visibleEndSideBounds ?: Rect()
    }

    private fun getStartSideIconBounds(): Rect {
        return statusBarBoundsProvider?.visibleStartSideBounds ?: Rect()
    }

    override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
        statusBarBoundsProvider = component.boundsProvider
    }

    private fun Rect.overlapsWith(other: Rect): Boolean {
        if (this.contains(other) || other.contains(this)) {
            return false
        }
        return this.intersect(other)
    }

    private fun dump(printWriter: PrintWriter) {
        printWriter.println(
            """
           lastAppearance: ${lastAppearance?.toAppearanceString()}
           lastAppearanceRegion: ${Arrays.toString(lastAppearanceRegions)},
           lastLetterboxes: ${Arrays.toString(lastLetterboxes)},
           lastLetterboxAppearance: $lastLetterboxAppearance
       """.trimIndent())
    }
}

private fun Int.toAppearanceString(): String =
    ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", this)
+15 −3
Original line number Diff line number Diff line
@@ -23,8 +23,8 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;

import android.annotation.ColorInt;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.InsetsFlags;
import android.view.ViewDebug;
@@ -63,7 +63,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
    private int mStatusBarMode;
    private int mNavigationBarMode;
    private int mNavigationMode;
    private final Color mDarkModeColor;
    private final int mDarkIconColor;
    private final int mLightIconColor;

    /**
     * Whether the navigation bar should be light factoring in already how much alpha the scrim has
@@ -94,7 +95,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
            BatteryController batteryController,
            NavigationModeController navModeController,
            DumpManager dumpManager) {
        mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
        mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone);
        mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone);
        mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
        mBatteryController = batteryController;
        mBatteryController.addCallback(this);
@@ -107,6 +109,16 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
        }
    }

    @ColorInt
    int getLightAppearanceIconColor() {
        return mDarkIconColor;
    }

    @ColorInt
    int getDarkAppearanceIconColor() {
        return mLightIconColor;
    }

    public void setNavigationBar(LightBarTransitionsController navigationBar) {
        mNavigationBarController = navigationBar;
        updateNavigation();
+9 −0
Original line number Diff line number Diff line
@@ -16,13 +16,22 @@

package com.android.systemui.statusbar.phone.dagger;

import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;

import java.util.Set;

import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoSet;
import dagger.multibindings.Multibinds;

@Module
interface CentralSurfacesStartableModule {
    @Multibinds
    Set<CentralSurfacesComponent.Startable> multibindStartables();

    @Binds
    @IntoSet
    CentralSurfacesComponent.Startable letterboxAppearanceCalculator(
            LetterboxAppearanceCalculator letterboxAppearanceCalculator);
}
+10 −0
Original line number Diff line number Diff line
@@ -46,10 +46,12 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -72,8 +74,10 @@ import java.util.concurrent.Executor;

import javax.inject.Named;

import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;

@Module(subcomponents = StatusBarFragmentComponent.class)
public abstract class StatusBarViewModule {
@@ -248,6 +252,12 @@ public abstract class StatusBarViewModule {
        return notificationShadeWindowView.findViewById(R.id.notification_container_parent);
    }

    @Binds
    @IntoSet
    abstract OnStatusBarViewInitializedListener statusBarInitializedListener(
            LetterboxAppearanceCalculator letterboxAppearanceCalculator
    );

    /**
     * Creates a new {@link CollapsedStatusBarFragment}.
     *
Loading