Add CustomizedLargeTopAppBar for Spa

With this, we can align the TopAppBar's font size & height with current
Settings.

Bug: 235727273
Test: Manually with Gallery
Test: Manually with Settings
Change-Id: Ieb60350b15c5f251f6369637b9eb572f78cc535b
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
new file mode 100644
index 0000000..7db1ca1
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -0,0 +1,604 @@
+/*
+ * Copyright 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationState
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.FastOutLinearInEasing
+import androidx.compose.animation.core.animateDecay
+import androidx.compose.animation.core.animateTo
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ProvideTextStyle
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.material3.TopAppBarState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.LastBaseline
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.compose.horizontalValues
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun CustomizedTopAppBar(
+    title: @Composable () -> Unit,
+    navigationIcon: @Composable () -> Unit = {},
+    actions: @Composable RowScope.() -> Unit = {},
+) {
+    SingleRowTopAppBar(
+        title = title,
+        titleTextStyle = MaterialTheme.typography.titleMedium,
+        navigationIcon = navigationIcon,
+        actions = actions,
+        windowInsets = TopAppBarDefaults.windowInsets,
+        colors = topAppBarColors(),
+    )
+}
+
+/**
+ * The customized LargeTopAppBar for Settings.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun CustomizedLargeTopAppBar(
+    title: String,
+    modifier: Modifier = Modifier,
+    navigationIcon: @Composable () -> Unit = {},
+    actions: @Composable RowScope.() -> Unit = {},
+    scrollBehavior: TopAppBarScrollBehavior? = null,
+) {
+    TwoRowsTopAppBar(
+        title = { Title(title = title, maxLines = 2) },
+        titleTextStyle = MaterialTheme.typography.displaySmall,
+        smallTitleTextStyle = MaterialTheme.typography.titleMedium,
+        titleBottomPadding = LargeTitleBottomPadding,
+        smallTitle = { Title(title = title, maxLines = 1) },
+        modifier = modifier,
+        navigationIcon = navigationIcon,
+        actions = actions,
+        colors = topAppBarColors(),
+        windowInsets = TopAppBarDefaults.windowInsets,
+        maxHeight = 176.dp,
+        pinnedHeight = ContainerHeight,
+        scrollBehavior = scrollBehavior,
+    )
+}
+
+@Composable
+private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
+    Text(
+        text = title,
+        modifier = Modifier
+            .padding(
+                WindowInsets.navigationBars
+                    .asPaddingValues()
+                    .horizontalValues()
+            )
+            .padding(horizontal = SettingsDimension.itemPaddingAround),
+        overflow = TextOverflow.Ellipsis,
+        maxLines = maxLines,
+    )
+}
+
+@Composable
+private fun topAppBarColors() = TopAppBarColors(
+    containerColor = MaterialTheme.colorScheme.background,
+    scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
+    navigationIconContentColor = MaterialTheme.colorScheme.onSurface,
+    titleContentColor = MaterialTheme.colorScheme.onSurface,
+    actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+)
+
+/**
+ * Represents the colors used by a top app bar in different states.
+ * This implementation animates the container color according to the top app bar scroll state. It
+ * does not animate the leading, headline, or trailing colors.
+ */
+@Stable
+private class TopAppBarColors(
+    val containerColor: Color,
+    val scrolledContainerColor: Color,
+    val navigationIconContentColor: Color,
+    val titleContentColor: Color,
+    val actionIconContentColor: Color,
+) {
+
+    /**
+     * Represents the container color used for the top app bar.
+     *
+     * A [colorTransitionFraction] provides a percentage value that can be used to generate a color.
+     * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from
+     * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction].
+     *
+     * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition
+     * percentage
+     */
+    @Composable
+    fun containerColor(colorTransitionFraction: Float): Color {
+        return lerp(
+            containerColor,
+            scrolledContainerColor,
+            FastOutLinearInEasing.transform(colorTransitionFraction)
+        )
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || other !is TopAppBarColors) return false
+
+        if (containerColor != other.containerColor) return false
+        if (scrolledContainerColor != other.scrolledContainerColor) return false
+        if (navigationIconContentColor != other.navigationIconContentColor) return false
+        if (titleContentColor != other.titleContentColor) return false
+        if (actionIconContentColor != other.actionIconContentColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = containerColor.hashCode()
+        result = 31 * result + scrolledContainerColor.hashCode()
+        result = 31 * result + navigationIconContentColor.hashCode()
+        result = 31 * result + titleContentColor.hashCode()
+        result = 31 * result + actionIconContentColor.hashCode()
+
+        return result
+    }
+}
+
+/**
+ * A single-row top app bar that is designed to be called by the small and center aligned top app
+ * bar composables.
+ */
+@Composable
+private fun SingleRowTopAppBar(
+    title: @Composable () -> Unit,
+    titleTextStyle: TextStyle,
+    navigationIcon: @Composable () -> Unit,
+    actions: @Composable (RowScope.() -> Unit),
+    windowInsets: WindowInsets,
+    colors: TopAppBarColors,
+) {
+    // Wrap the given actions in a Row.
+    val actionsRow = @Composable {
+        Row(
+            horizontalArrangement = Arrangement.End,
+            verticalAlignment = Alignment.CenterVertically,
+            content = actions
+        )
+    }
+
+    // Compose a Surface with a TopAppBarLayout content.
+    Surface(color = colors.scrolledContainerColor) {
+        val height = LocalDensity.current.run { ContainerHeight.toPx() }
+        TopAppBarLayout(
+            modifier = Modifier
+                .windowInsetsPadding(windowInsets)
+                // clip after padding so we don't show the title over the inset area
+                .clipToBounds(),
+            heightPx = height,
+            navigationIconContentColor = colors.navigationIconContentColor,
+            titleContentColor = colors.titleContentColor,
+            actionIconContentColor = colors.actionIconContentColor,
+            title = title,
+            titleTextStyle = titleTextStyle,
+            titleAlpha = 1f,
+            titleVerticalArrangement = Arrangement.Center,
+            titleHorizontalArrangement = Arrangement.Start,
+            titleBottomPadding = 0,
+            hideTitleSemantics = false,
+            navigationIcon = navigationIcon,
+            actions = actionsRow,
+        )
+    }
+}
+
+/**
+ * A two-rows top app bar that is designed to be called by the Large and Medium top app bar
+ * composables.
+ *
+ * @throws [IllegalArgumentException] if the given [maxHeight] is equal or smaller than the
+ * [pinnedHeight]
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun TwoRowsTopAppBar(
+    modifier: Modifier = Modifier,
+    title: @Composable () -> Unit,
+    titleTextStyle: TextStyle,
+    titleBottomPadding: Dp,
+    smallTitle: @Composable () -> Unit,
+    smallTitleTextStyle: TextStyle,
+    navigationIcon: @Composable () -> Unit,
+    actions: @Composable RowScope.() -> Unit,
+    windowInsets: WindowInsets,
+    colors: TopAppBarColors,
+    maxHeight: Dp,
+    pinnedHeight: Dp,
+    scrollBehavior: TopAppBarScrollBehavior?
+) {
+    if (maxHeight <= pinnedHeight) {
+        throw IllegalArgumentException(
+            "A TwoRowsTopAppBar max height should be greater than its pinned height"
+        )
+    }
+    val pinnedHeightPx: Float
+    val maxHeightPx: Float
+    val titleBottomPaddingPx: Int
+    LocalDensity.current.run {
+        pinnedHeightPx = pinnedHeight.toPx()
+        maxHeightPx = maxHeight.toPx()
+        titleBottomPaddingPx = titleBottomPadding.roundToPx()
+    }
+
+    // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
+    // visible when collapsed.
+    SideEffect {
+        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
+            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
+        }
+    }
+
+    // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the
+    // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or
+    // collapse.
+    // This will potentially animate or interpolate a transition between the container color and the
+    // container's scrolled color according to the app bar's scroll state.
+    val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f
+    val appBarContainerColor by rememberUpdatedState(colors.containerColor(colorTransitionFraction))
+
+    // Wrap the given actions in a Row.
+    val actionsRow = @Composable {
+        Row(
+            horizontalArrangement = Arrangement.End,
+            verticalAlignment = Alignment.CenterVertically,
+            content = actions
+        )
+    }
+    val topTitleAlpha = TopTitleAlphaEasing.transform(colorTransitionFraction)
+    val bottomTitleAlpha = 1f - colorTransitionFraction
+    // Hide the top row title semantics when its alpha value goes below 0.5 threshold.
+    // Hide the bottom row title semantics when the top title semantics are active.
+    val hideTopRowSemantics = colorTransitionFraction < 0.5f
+    val hideBottomRowSemantics = !hideTopRowSemantics
+
+    // Set up support for resizing the top app bar when vertically dragging the bar itself.
+    val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) {
+        Modifier.draggable(
+            orientation = Orientation.Vertical,
+            state = rememberDraggableState { delta ->
+                scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta
+            },
+            onDragStopped = { velocity ->
+                settleAppBar(
+                    scrollBehavior.state,
+                    velocity,
+                    scrollBehavior.flingAnimationSpec,
+                    scrollBehavior.snapAnimationSpec
+                )
+            }
+        )
+    } else {
+        Modifier
+    }
+
+    Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) {
+        Column {
+            TopAppBarLayout(
+                modifier = Modifier
+                    .windowInsetsPadding(windowInsets)
+                    // clip after padding so we don't show the title over the inset area
+                    .clipToBounds(),
+                heightPx = pinnedHeightPx,
+                navigationIconContentColor = colors.navigationIconContentColor,
+                titleContentColor = colors.titleContentColor,
+                actionIconContentColor = colors.actionIconContentColor,
+                title = smallTitle,
+                titleTextStyle = smallTitleTextStyle,
+                titleAlpha = topTitleAlpha,
+                titleVerticalArrangement = Arrangement.Center,
+                titleHorizontalArrangement = Arrangement.Start,
+                titleBottomPadding = 0,
+                hideTitleSemantics = hideTopRowSemantics,
+                navigationIcon = navigationIcon,
+                actions = actionsRow,
+            )
+            TopAppBarLayout(
+                modifier = Modifier.clipToBounds(),
+                heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
+                    ?: 0f),
+                navigationIconContentColor = colors.navigationIconContentColor,
+                titleContentColor = colors.titleContentColor,
+                actionIconContentColor = colors.actionIconContentColor,
+                title = title,
+                titleTextStyle = titleTextStyle,
+                titleAlpha = bottomTitleAlpha,
+                titleVerticalArrangement = Arrangement.Bottom,
+                titleHorizontalArrangement = Arrangement.Start,
+                titleBottomPadding = titleBottomPaddingPx,
+                hideTitleSemantics = hideBottomRowSemantics,
+                navigationIcon = {},
+                actions = {}
+            )
+        }
+    }
+}
+
+/**
+ * The base [Layout] for all top app bars. This function lays out a top app bar navigation icon
+ * (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and
+ * the actions are optional.
+ *
+ * @param heightPx the total height this layout is capped to
+ * @param navigationIconContentColor the content color that will be applied via a
+ * [LocalContentColor] when composing the navigation icon
+ * @param titleContentColor the color that will be applied via a [LocalContentColor] when composing
+ * the title
+ * @param actionIconContentColor the content color that will be applied via a [LocalContentColor]
+ * when composing the action icons
+ * @param title the top app bar title (header)
+ * @param titleTextStyle the title's text style
+ * @param modifier a [Modifier]
+ * @param titleAlpha the title's alpha
+ * @param titleVerticalArrangement the title's vertical arrangement
+ * @param titleHorizontalArrangement the title's horizontal arrangement
+ * @param titleBottomPadding the title's bottom padding
+ * @param hideTitleSemantics hides the title node from the semantic tree. Apply this
+ * boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics
+ * from accessibility services. This is needed to avoid having multiple titles visible to
+ * accessibility services at the same time, when animating between collapsed / expanded states.
+ * @param navigationIcon a navigation icon [Composable]
+ * @param actions actions [Composable]
+ */
+@Composable
+private fun TopAppBarLayout(
+    modifier: Modifier,
+    heightPx: Float,
+    navigationIconContentColor: Color,
+    titleContentColor: Color,
+    actionIconContentColor: Color,
+    title: @Composable () -> Unit,
+    titleTextStyle: TextStyle,
+    titleAlpha: Float,
+    titleVerticalArrangement: Arrangement.Vertical,
+    titleHorizontalArrangement: Arrangement.Horizontal,
+    titleBottomPadding: Int,
+    hideTitleSemantics: Boolean,
+    navigationIcon: @Composable () -> Unit,
+    actions: @Composable () -> Unit,
+) {
+    Layout(
+        {
+            Box(
+                Modifier
+                    .layoutId("navigationIcon")
+                    .padding(start = TopAppBarHorizontalPadding)
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides navigationIconContentColor,
+                    content = navigationIcon
+                )
+            }
+            Box(
+                Modifier
+                    .layoutId("title")
+                    .padding(horizontal = TopAppBarHorizontalPadding)
+                    .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics { } else Modifier)
+                    .graphicsLayer(alpha = titleAlpha)
+            ) {
+                ProvideTextStyle(value = titleTextStyle) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides titleContentColor,
+                        content = title
+                    )
+                }
+            }
+            Box(
+                Modifier
+                    .layoutId("actionIcons")
+                    .padding(end = TopAppBarHorizontalPadding)
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides actionIconContentColor,
+                    content = actions
+                )
+            }
+        },
+        modifier = modifier
+    ) { measurables, constraints ->
+        val navigationIconPlaceable =
+            measurables.first { it.layoutId == "navigationIcon" }
+                .measure(constraints.copy(minWidth = 0))
+        val actionIconsPlaceable =
+            measurables.first { it.layoutId == "actionIcons" }
+                .measure(constraints.copy(minWidth = 0))
+
+        val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) {
+            constraints.maxWidth
+        } else {
+            (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width)
+                .coerceAtLeast(0)
+        }
+        val titlePlaceable =
+            measurables.first { it.layoutId == "title" }
+                .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth))
+
+        // Locate the title's baseline.
+        val titleBaseline =
+            if (titlePlaceable[LastBaseline] != AlignmentLine.Unspecified) {
+                titlePlaceable[LastBaseline]
+            } else {
+                0
+            }
+
+        val layoutHeight = heightPx.roundToInt()
+
+        layout(constraints.maxWidth, layoutHeight) {
+            // Navigation icon
+            navigationIconPlaceable.placeRelative(
+                x = 0,
+                y = (layoutHeight - navigationIconPlaceable.height) / 2
+            )
+
+            // Title
+            titlePlaceable.placeRelative(
+                x = when (titleHorizontalArrangement) {
+                    Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2
+                    Arrangement.End ->
+                        constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width
+                    // Arrangement.Start.
+                    // A TopAppBarTitleInset will make sure the title is offset in case the
+                    // navigation icon is missing.
+                    else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width)
+                },
+                y = when (titleVerticalArrangement) {
+                    Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
+                    // Apply bottom padding from the title's baseline only when the Arrangement is
+                    // "Bottom".
+                    Arrangement.Bottom ->
+                        if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height
+                        else layoutHeight - titlePlaceable.height - max(
+                            0,
+                            titleBottomPadding - titlePlaceable.height + titleBaseline
+                        )
+                    // Arrangement.Top
+                    else -> 0
+                }
+            )
+
+            // Action icons
+            actionIconsPlaceable.placeRelative(
+                x = constraints.maxWidth - actionIconsPlaceable.width,
+                y = (layoutHeight - actionIconsPlaceable.height) / 2
+            )
+        }
+    }
+}
+
+
+/**
+ * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping
+ * after the fling settles.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+private suspend fun settleAppBar(
+    state: TopAppBarState,
+    velocity: Float,
+    flingAnimationSpec: DecayAnimationSpec<Float>?,
+    snapAnimationSpec: AnimationSpec<Float>?
+): Velocity {
+    // Check if the app bar is completely collapsed/expanded. If so, no need to settle the app bar,
+    // and just return Zero Velocity.
+    // Note that we don't check for 0f due to float precision with the collapsedFraction
+    // calculation.
+    if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) {
+        return Velocity.Zero
+    }
+    var remainingVelocity = velocity
+    // In case there is an initial velocity that was left after a previous user fling, animate to
+    // continue the motion to expand or collapse the app bar.
+    if (flingAnimationSpec != null && abs(velocity) > 1f) {
+        var lastValue = 0f
+        AnimationState(
+            initialValue = 0f,
+            initialVelocity = velocity,
+        )
+            .animateDecay(flingAnimationSpec) {
+                val delta = value - lastValue
+                val initialHeightOffset = state.heightOffset
+                state.heightOffset = initialHeightOffset + delta
+                val consumed = abs(initialHeightOffset - state.heightOffset)
+                lastValue = value
+                remainingVelocity = this.velocity
+                // avoid rounding errors and stop if anything is unconsumed
+                if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
+            }
+    }
+    // Snap if animation specs were provided.
+    if (snapAnimationSpec != null) {
+        if (state.heightOffset < 0 &&
+            state.heightOffset > state.heightOffsetLimit
+        ) {
+            AnimationState(initialValue = state.heightOffset).animateTo(
+                if (state.collapsedFraction < 0.5f) {
+                    0f
+                } else {
+                    state.heightOffsetLimit
+                },
+                animationSpec = snapAnimationSpec
+            ) { state.heightOffset = value }
+        }
+    }
+
+    return Velocity(0f, remainingVelocity)
+}
+
+// An easing function used to compute the alpha value that is applied to the top title part of a
+// Medium or Large app bar.
+private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f)
+
+private val ContainerHeight = 56.dp
+private val LargeTitleBottomPadding = 28.dp
+private val TopAppBarHorizontalPadding = 4.dp
+
+// A title inset when the App-Bar is a Medium or Large one. Also used to size a spacer when the
+// navigation icon is missing.
+private val TopAppBarTitleInset = 16.dp - TopAppBarHorizontalPadding
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index efc623a..b4852e4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -24,17 +24,14 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextField
 import androidx.compose.material3.TextFieldDefaults
-import androidx.compose.material3.TopAppBar
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.material3.TopAppBarScrollBehavior
 import androidx.compose.runtime.Composable
@@ -136,7 +133,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun SearchTopAppBar(
     query: TextFieldValue,
@@ -144,20 +140,16 @@
     onClose: () -> Unit,
     actions: @Composable RowScope.() -> Unit = {},
 ) {
-    Surface(color = SettingsTheme.colorScheme.surfaceHeader) {
-        TopAppBar(
-            title = { SearchBox(query, onQueryChange) },
-            modifier = Modifier.statusBarsPadding(),
-            navigationIcon = { CollapseAction(onClose) },
-            actions = {
-                if (query.text.isNotEmpty()) {
-                    ClearAction { onQueryChange(TextFieldValue()) }
-                }
-                actions()
-            },
-            colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Transparent),
-        )
-    }
+    CustomizedTopAppBar(
+        title = { SearchBox(query, onQueryChange) },
+        navigationIcon = { CollapseAction(onClose) },
+        actions = {
+            if (query.text.isNotEmpty()) {
+                ClearAction { onQueryChange(TextFieldValue()) }
+            }
+            actions()
+        },
+    )
     BackHandler { onClose() }
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
index f7cb035..3311792 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
@@ -17,24 +17,9 @@
 package com.android.settingslib.spa.widget.scaffold
 
 import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.navigationBars
-import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.LargeTopAppBar
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.material3.TopAppBarScrollBehavior
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextOverflow
-import com.android.settingslib.spa.framework.compose.horizontalValues
-import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.theme.rememberSettingsTypography
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
@@ -43,20 +28,12 @@
     scrollBehavior: TopAppBarScrollBehavior,
     actions: @Composable RowScope.() -> Unit,
 ) {
-    val colorScheme = MaterialTheme.colorScheme
-    // TODO: Remove MaterialTheme() after top app bar color fixed in AndroidX.
-    MaterialTheme(
-        colorScheme = remember { colorScheme.copy(surface = colorScheme.background) },
-        typography = rememberSettingsTypography(),
-    ) {
-        LargeTopAppBar(
-            title = { Title(title) },
-            navigationIcon = { NavigateBack() },
-            actions = actions,
-            colors = largeTopAppBarColors(),
-            scrollBehavior = scrollBehavior,
-        )
-    }
+    CustomizedLargeTopAppBar(
+        title = title,
+        navigationIcon = { NavigateBack() },
+        actions = actions,
+        scrollBehavior = scrollBehavior,
+    )
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
@@ -65,22 +42,3 @@
         heightOffset = heightOffsetLimit
     }
 }
-
-@Composable
-private fun Title(title: String) {
-    Text(
-        text = title,
-        modifier = Modifier
-            .padding(WindowInsets.navigationBars.asPaddingValues().horizontalValues())
-            .padding(SettingsDimension.itemPaddingAround),
-        overflow = TextOverflow.Ellipsis,
-        maxLines = 1,
-    )
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-private fun largeTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors(
-    containerColor = MaterialTheme.colorScheme.background,
-    scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
-)