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

Commit 921f724c authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 13599671 from 6328bd56 to 25Q3-release

Change-Id: I940ba5e3038388354c56d1f17887d1c75673ff46
parents 76f02816 6328bd56
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -151,6 +151,14 @@
        [CHAR LIMIT=15] -->
    <string name="app_icons_theme_themed">themed</string>

    <!-- Label for an icon style option that applies no theming to app icons when selected.
        [CHAR LIMIT=15] -->
    <string name="app_icons_style_default">Default</string>

    <!-- Label for an icon style option that applies a monochrome theme to app icons when selected.
        [CHAR LIMIT=15] -->
    <string name="app_icons_style_minimal">Minimal</string>

    <!-- Title of a tab to change the app icon shape [CHAR LIMIT=15] -->
    <string name="app_icons_shape">Shape</string>

@@ -653,4 +661,16 @@
    [CHAR LIMIT=128].
    -->
    <string name="custom_clocks_label">Custom Clocks</string>

    <!--
    Accessibility label for the Clock face width label.
    -->
    <string name="clock_face_width">Clock face width</string>

    <!--
    Accessibility announcement template for a numerical range.
    %1$d is the minimum value, %2$d is the maximum value.
    Example: "range from 1 to 7". [CHAR LIMIT=none]
    -->
    <string name="range_announcement_template">range from %1$d to %2$d</string>
</resources>
+18 −2
Original line number Diff line number Diff line
@@ -12,13 +12,13 @@
 * 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.customization.picker.grid.domain.interactor
package com.android.customization.picker.icon.domain.interactor

import com.android.customization.model.grid.ShapeOptionModel
import com.android.customization.picker.grid.data.repository.ShapeRepository
import com.android.customization.picker.icon.shared.model.IconStyle
import com.android.customization.picker.themedicon.data.repository.ThemedIconRepository
import javax.inject.Inject
import javax.inject.Singleton
@@ -45,6 +45,22 @@ constructor(

    val isThemedIconEnabled: Flow<Boolean> = themedIconRepository.isActivated

    val iconStyles =
        isThemedIconAvailable.map { isThemedIconAvailable ->
            // TODO (b/397782741): introduce different icon styles depending on repository
            var styles = IconStyle.entries.toList()
            if (!isThemedIconAvailable) styles = styles.filter { it != IconStyle.MONOCHROME }
            styles
        }

    val selectedIconStyle =
        isThemedIconEnabled.map {
            when (it) {
                true -> IconStyle.MONOCHROME
                false -> IconStyle.DEFAULT
            }
        }

    suspend fun applyThemedIconEnabled(enabled: Boolean) =
        themedIconRepository.setThemedIconEnabled(enabled)

+24 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.customization.picker.icon.shared.model

import com.android.themepicker.R

enum class IconStyle(val nameResId: Int) {
    DEFAULT(R.string.app_icons_style_default),
    MONOCHROME(R.string.app_icons_style_minimal),
}
+61 −5
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.wallpaper.customization.ui.binder

import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.AdaptiveIconDrawable
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
@@ -32,6 +33,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.customization.picker.common.ui.view.SingleRowListItemSpacing
import com.android.customization.picker.grid.ui.viewmodel.ShapeIconViewModel
import com.android.customization.picker.icon.shared.model.IconStyle
import com.android.themepicker.R
import com.android.wallpaper.config.BaseFlags
import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption.APP_ICONS
@@ -112,7 +114,20 @@ object AppIconFloatingSheetBinder {
            )
        val shapeOptionList =
            view.requireViewById<RecyclerView>(R.id.shape_options).also {
                it.initShapeOptionList(view.context, shapeOptionListAdapter)
                it.initOptionList(view.context, shapeOptionListAdapter)
            }

        val styleOptionListAdapter =
            createStyleOptionItemAdapter(
                context = view.context,
                colorUpdateViewModel = colorUpdateViewModel,
                shouldAnimateColor = isFloatingSheetActive,
                lifecycleOwner = lifecycleOwner,
                backgroundDispatcher = backgroundDispatcher,
            )
        val styleOptionList =
            view.requireViewById<RecyclerView>(R.id.icon_style_options).also {
                it.initOptionList(view.context, styleOptionListAdapter)
            }

        val themedIconsSwitch = view.requireViewById<MaterialSwitch>(R.id.themed_icon_toggle)
@@ -137,6 +152,17 @@ object AppIconFloatingSheetBinder {
                if (isExtendibleThemeManager) {
                    themedIconEntry.isVisible = false

                    launch {
                        viewModel.styleOptions.collect { options ->
                            styleOptionListAdapter.setItems(options) {
                                val indexToFocus =
                                    options.indexOfFirst { it.isSelected.value }.coerceAtLeast(0)
                                (styleOptionList.layoutManager as LinearLayoutManager)
                                    .scrollToPosition(indexToFocus)
                            }
                        }
                    }

                    launch {
                        viewModel.tabs.collect {
                            if (it.size > 1) {
@@ -245,6 +271,39 @@ object AppIconFloatingSheetBinder {
        }
    }

    private fun createStyleOptionItemAdapter(
        context: Context,
        colorUpdateViewModel: ColorUpdateViewModel,
        shouldAnimateColor: () -> Boolean,
        lifecycleOwner: LifecycleOwner,
        backgroundDispatcher: CoroutineDispatcher,
    ): OptionItemAdapter2<IconStyle> {
        val previewIconPackageName = context.resources.getString(R.string.camera_package)
        val appIconDrawable = ShapeIconViewBinder.loadAppIcon(context, previewIconPackageName)
        return OptionItemAdapter2(
            layoutResourceId = R.layout.icon_style_option2,
            lifecycleOwner = lifecycleOwner,
            backgroundDispatcher = backgroundDispatcher,
            bindPayload = { view: View, iconStyle: IconStyle ->
                val imageView = view.findViewById(R.id.foreground) as? ImageView
                val disposableHandle =
                    imageView?.let {
                        ShapeIconViewBinder.bindPreviewIcon(
                            view = it,
                            appIconDrawable = appIconDrawable as? AdaptiveIconDrawable,
                            isThemed = iconStyle == IconStyle.MONOCHROME,
                            colorUpdateViewModel = colorUpdateViewModel,
                            shouldAnimateColor = shouldAnimateColor,
                            lifecycleOwner = lifecycleOwner,
                        )
                    }
                return@OptionItemAdapter2 disposableHandle
            },
            colorUpdateViewModel = WeakReference(colorUpdateViewModel),
            shouldAnimateColor = shouldAnimateColor,
        )
    }

    private fun createShapeOptionItemAdapter(
        colorUpdateViewModel: ColorUpdateViewModel,
        shouldAnimateColor: () -> Boolean,
@@ -264,10 +323,7 @@ object AppIconFloatingSheetBinder {
            shouldAnimateColor = shouldAnimateColor,
        )

    private fun RecyclerView.initShapeOptionList(
        context: Context,
        adapter: OptionItemAdapter2<ShapeIconViewModel>,
    ) {
    private fun RecyclerView.initOptionList(context: Context, adapter: OptionItemAdapter2<*>) {
        apply {
            this.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
            addItemDecoration(
+57 −1
Original line number Diff line number Diff line
@@ -23,10 +23,12 @@ import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.core.view.get
import androidx.core.view.isEmpty
import androidx.core.view.isVisible
@@ -137,7 +139,61 @@ object ClockFloatingSheetBinder {
        val axisPresetSlider: Slider =
            clockStyleContent.requireViewById(R.id.clock_axis_preset_slider)

        // Clock color
        // Setting content description for the clock face width slider
        val sliderLabel = appContext.getString(R.string.clock_face_width)
        axisPresetSlider.contentDescription = sliderLabel
        axisPresetSlider.setAccessibilityDelegate(
            object : View.AccessibilityDelegate() {
                override fun onInitializeAccessibilityNodeInfo(
                    host: View,
                    info: AccessibilityNodeInfo,
                ) {
                    super.onInitializeAccessibilityNodeInfo(host, info)
                    val infoCompat = AccessibilityNodeInfoCompat.wrap(info)

                    if (host !is Slider) return

                    // increased these values by 1.0 in order to announce for content description
                    val actualMin = host.valueFrom + 1.0f
                    val actualMax = host.valueTo + 1.0f
                    val currentValueActual = host.value + 1.0f

                    val normalizedPosition =
                        if (actualMax - actualMin == 0f) {
                            0f
                        } else {
                            ((currentValueActual - actualMin) / (actualMax - actualMin)).coerceIn(
                                0f,
                                1f,
                            )
                        }
                    val mappedValueFloat = actualMin + normalizedPosition * (actualMax - actualMin)
                    val mappedValueInt = Math.round(mappedValueFloat)

                    val conceptualMinInt = actualMin.toInt()
                    val conceptualMaxInt = actualMax.toInt()
                    // this is a workaround that is needed because talkback isn't announcing the
                    // range using RangeInfo on the material slider.
                    val hardcodedRangeText =
                        appContext.getString(
                            R.string.range_announcement_template,
                            conceptualMinInt,
                            conceptualMaxInt,
                        )
                    val customRangeInfo =
                        AccessibilityNodeInfoCompat.RangeInfoCompat.obtain(
                            AccessibilityNodeInfoCompat.RangeInfoCompat.RANGE_TYPE_INT,
                            actualMin,
                            actualMax,
                            mappedValueInt.toFloat(),
                        )
                    infoCompat.rangeInfo = customRangeInfo
                    infoCompat.text = null
                    infoCompat.stateDescription = "$mappedValueInt, $hardcodedRangeText"
                }
            }
        )

        val clockColorContent: View = view.requireViewById(R.id.clock_floating_sheet_color_content)

        val clockColorAdapter =
Loading