Loading res/values/strings.xml +20 −0 Original line number Diff line number Diff line Loading @@ -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> Loading Loading @@ -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> src/com/android/customization/picker/grid/domain/interactor/AppIconInteractor.kt→src/com/android/customization/picker/icon/domain/interactor/AppIconInteractor.kt +18 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading src/com/android/customization/picker/icon/shared/model/IconStyle.kt 0 → 100644 +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), } src/com/android/wallpaper/customization/ui/binder/AppIconFloatingSheetBinder.kt +61 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) { Loading Loading @@ -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, Loading @@ -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( Loading src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt +57 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading
res/values/strings.xml +20 −0 Original line number Diff line number Diff line Loading @@ -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> Loading Loading @@ -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>
src/com/android/customization/picker/grid/domain/interactor/AppIconInteractor.kt→src/com/android/customization/picker/icon/domain/interactor/AppIconInteractor.kt +18 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading
src/com/android/customization/picker/icon/shared/model/IconStyle.kt 0 → 100644 +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), }
src/com/android/wallpaper/customization/ui/binder/AppIconFloatingSheetBinder.kt +61 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) { Loading Loading @@ -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, Loading @@ -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( Loading
src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt +57 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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