summaryrefslogtreecommitdiff
path: root/src/com/android/customization/picker
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/customization/picker')
-rw-r--r--src/com/android/customization/picker/WallpaperPreviewer.java2
-rw-r--r--src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt3
-rw-r--r--src/com/android/customization/picker/clock/shared/ClockSize.kt11
-rw-r--r--src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt1
-rw-r--r--src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt10
-rw-r--r--src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt53
-rw-r--r--src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt81
-rw-r--r--src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt20
-rw-r--r--src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt62
-rw-r--r--src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt4
-rw-r--r--src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt50
-rw-r--r--src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt212
-rw-r--r--src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt240
-rw-r--r--src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt22
-rw-r--r--src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt26
-rw-r--r--src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt51
-rw-r--r--src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt42
-rw-r--r--src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt12
-rw-r--r--src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt81
-rw-r--r--src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt2
-rw-r--r--src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt81
-rw-r--r--src/com/android/customization/picker/color/ui/section/ColorSectionController.kt (renamed from src/com/android/customization/picker/color/ui/section/ColorSectionController2.kt)14
-rw-r--r--src/com/android/customization/picker/color/ui/view/ColorSectionView.kt (renamed from src/com/android/customization/picker/color/ui/view/ColorSectionView2.kt)2
-rw-r--r--src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt12
-rw-r--r--src/com/android/customization/picker/grid/GridFragment.java299
-rw-r--r--src/com/android/customization/picker/grid/GridOptionPreviewer.java105
-rw-r--r--src/com/android/customization/picker/grid/data/repository/GridRepository.kt181
-rw-r--r--src/com/android/customization/picker/grid/domain/interactor/GridInteractor.kt110
-rw-r--r--src/com/android/customization/picker/grid/domain/interactor/GridSnapshotRestorer.kt74
-rw-r--r--src/com/android/customization/picker/grid/shared/model/GridOptionItemModel.kt28
-rw-r--r--src/com/android/customization/picker/grid/shared/model/GridOptionItemsModel.kt (renamed from src/com/android/customization/picker/clock/utils/ClockDescriptionUtils.kt)17
-rw-r--r--src/com/android/customization/picker/grid/ui/binder/GridIconViewBinder.kt17
-rw-r--r--src/com/android/customization/picker/grid/ui/binder/GridScreenBinder.kt91
-rw-r--r--src/com/android/customization/picker/grid/ui/fragment/GridFragment.kt210
-rw-r--r--src/com/android/customization/picker/grid/ui/section/GridSectionController.java131
-rw-r--r--src/com/android/customization/picker/grid/ui/view/GridSectionView.java (renamed from src/com/android/customization/picker/grid/GridSectionView.java)2
-rw-r--r--src/com/android/customization/picker/grid/ui/viewmodel/GridIconViewModel.kt (renamed from src/com/android/customization/picker/clock/utils/ThemePickerClockDescriptionUtils.kt)14
-rw-r--r--src/com/android/customization/picker/grid/ui/viewmodel/GridScreenViewModel.kt106
-rw-r--r--src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt11
-rw-r--r--src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt6
-rw-r--r--src/com/android/customization/picker/preview/ui/section/PreviewWithThemeSectionController.kt40
-rw-r--r--src/com/android/customization/picker/quickaffordance/data/repository/KeyguardQuickAffordancePickerRepository.kt38
-rw-r--r--src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordancePickerInteractor.kt12
-rw-r--r--src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordanceSnapshotRestorer.kt9
-rw-r--r--src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt4
-rw-r--r--src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt24
-rw-r--r--src/com/android/customization/picker/quickaffordance/ui/section/KeyguardQuickAffordanceSectionController.kt8
-rw-r--r--src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt39
-rw-r--r--src/com/android/customization/picker/theme/CustomThemeActivity.java421
-rw-r--r--src/com/android/customization/picker/theme/CustomThemeComponentFragment.java121
-rw-r--r--src/com/android/customization/picker/theme/CustomThemeNameFragment.java132
-rw-r--r--src/com/android/customization/picker/theme/CustomThemeStepFragment.java116
-rw-r--r--src/com/android/customization/picker/theme/ThemeFragment.java402
-rw-r--r--src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java165
-rw-r--r--src/com/android/customization/picker/theme/ThemeInfoView.java84
-rw-r--r--src/com/android/customization/picker/theme/ThemeOptionPreviewer.java405
-rw-r--r--src/com/android/customization/picker/themedicon/ThemedIconSectionView.java14
57 files changed, 1648 insertions, 2882 deletions
diff --git a/src/com/android/customization/picker/WallpaperPreviewer.java b/src/com/android/customization/picker/WallpaperPreviewer.java
index d74bfaea..18bc89c7 100644
--- a/src/com/android/customization/picker/WallpaperPreviewer.java
+++ b/src/com/android/customization/picker/WallpaperPreviewer.java
@@ -241,7 +241,7 @@ public class WallpaperPreviewer implements LifecycleObserver {
() -> mFadeInScrim.setVisibility(View.INVISIBLE));
}
}
- }, mWallpaperSurface, WallpaperConnection.WHICH_PREVIEW.PREVIEW_CURRENT);
+ }, mWallpaperSurface, WallpaperConnection.WhichPreview.PREVIEW_CURRENT);
mWallpaperConnection.setVisibility(true);
mHomePreview.post(() -> {
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
index 004103f3..cc4079a1 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -21,7 +21,7 @@ import androidx.annotation.ColorInt
import androidx.annotation.IntRange
import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
-import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.wallpaper.settings.data.repository.SecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
@@ -187,7 +187,6 @@ class ClockPickerRepositoryImpl(
): ClockMetadataModel {
return ClockMetadataModel(
clockId = clockId,
- name = name,
isSelected = isSelected,
selectedColorId = selectedColorId,
colorToneProgress = colorTone,
diff --git a/src/com/android/customization/picker/clock/shared/ClockSize.kt b/src/com/android/customization/picker/clock/shared/ClockSize.kt
index 279ee54b..9b35f73f 100644
--- a/src/com/android/customization/picker/clock/shared/ClockSize.kt
+++ b/src/com/android/customization/picker/clock/shared/ClockSize.kt
@@ -16,7 +16,18 @@
*/
package com.android.customization.picker.clock.shared
+import android.stats.style.StyleEnums
+import com.android.customization.module.logging.ThemesUserEventLogger
+
enum class ClockSize {
DYNAMIC,
SMALL,
}
+
+@ThemesUserEventLogger.ClockSize
+fun ClockSize.toClockSizeForLogging(): Int {
+ return when (this) {
+ ClockSize.DYNAMIC -> StyleEnums.CLOCK_SIZE_DYNAMIC
+ ClockSize.SMALL -> StyleEnums.CLOCK_SIZE_SMALL
+ }
+}
diff --git a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
index 25225075..6e2bfb38 100644
--- a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
+++ b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
@@ -23,7 +23,6 @@ import androidx.annotation.IntRange
/** Model for clock metadata. */
data class ClockMetadataModel(
val clockId: String,
- val name: String,
val isSelected: Boolean,
val selectedColorId: String?,
@IntRange(from = 0, to = 100) val colorToneProgress: Int,
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
index 6bd717b7..e2616c76 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
@@ -58,11 +58,6 @@ object ClockCarouselViewBinder {
carouselView.transitionToPrevious()
}
)
- screenPreviewClickView.accessibilityDelegate = carouselAccessibilityDelegate
- screenPreviewClickView.setOnSideClickedListener { isStart ->
- if (isStart) carouselView.scrollToPrevious() else carouselView.scrollToNext()
- }
-
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
@@ -76,6 +71,11 @@ object ClockCarouselViewBinder {
},
isTwoPaneAndSmallWidth = isTwoPaneAndSmallWidth,
)
+ screenPreviewClickView.accessibilityDelegate = carouselAccessibilityDelegate
+ screenPreviewClickView.setOnSideClickedListener { isStart ->
+ if (isStart) carouselView.scrollToPrevious()
+ else carouselView.scrollToNext()
+ }
}
}
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt
deleted file mode 100644
index 7dc0d0c4..00000000
--- a/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2023 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.clock.ui.binder
-
-import android.view.View
-import android.widget.TextView
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
-import com.android.wallpaper.R
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-
-object ClockSectionViewBinder {
- fun bind(
- view: View,
- viewModel: ClockSectionViewModel,
- lifecycleOwner: LifecycleOwner,
- onClicked: () -> Unit,
- ) {
- view.setOnClickListener { onClicked() }
-
- val selectedClockColorAndSize: TextView =
- view.requireViewById(R.id.selected_clock_color_and_size)
-
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.selectedClockColorAndSizeText.collectLatest {
- selectedClockColorAndSize.text = it
- }
- }
- }
- }
- }
-}
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
index 6e745d54..d17cdf8a 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
@@ -15,11 +15,18 @@
*/
package com.android.customization.picker.clock.ui.binder
+import android.content.Context
import android.content.res.Configuration
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.style.TextAppearanceSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
+import android.widget.RadioButton
+import android.widget.RadioGroup
+import android.widget.RadioGroup.OnCheckedChangeListener
import android.widget.SeekBar
import androidx.core.view.doOnPreDraw
import androidx.core.view.isInvisible
@@ -35,7 +42,6 @@ import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.ui.adapter.ClockSettingsTabAdapter
import com.android.customization.picker.clock.ui.view.ClockCarouselView
import com.android.customization.picker.clock.ui.view.ClockHostView
-import com.android.customization.picker.clock.ui.view.ClockSizeRadioButtonGroup
import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
import com.android.customization.picker.color.ui.binder.ColorOptionIconBinder
@@ -83,14 +89,27 @@ object ClockSettingsBinder {
}
)
- val sizeOptions =
- view.requireViewById<ClockSizeRadioButtonGroup>(R.id.clock_size_radio_button_group)
- sizeOptions.onRadioButtonClickListener =
- object : ClockSizeRadioButtonGroup.OnRadioButtonClickListener {
- override fun onClick(size: ClockSize) {
- viewModel.setClockSize(size)
- }
+ val onCheckedChangeListener = OnCheckedChangeListener { _, id ->
+ when (id) {
+ R.id.radio_dynamic -> viewModel.setClockSize(ClockSize.DYNAMIC)
+ R.id.radio_small -> viewModel.setClockSize(ClockSize.SMALL)
}
+ }
+ val clockSizeRadioGroup =
+ view.requireViewById<RadioGroup>(R.id.clock_size_radio_button_group)
+ clockSizeRadioGroup.setOnCheckedChangeListener(onCheckedChangeListener)
+ view.requireViewById<RadioButton>(R.id.radio_dynamic).text =
+ getRadioText(
+ view.context.applicationContext,
+ view.resources.getString(R.string.clock_size_dynamic),
+ view.resources.getString(R.string.clock_size_dynamic_description)
+ )
+ view.requireViewById<RadioButton>(R.id.radio_small).text =
+ getRadioText(
+ view.context.applicationContext,
+ view.resources.getString(R.string.clock_size_small),
+ view.resources.getString(R.string.clock_size_small_description)
+ )
val colorOptionContainer = view.requireViewById<View>(R.id.color_picker_container)
lifecycleOwner.lifecycleScope.launch {
@@ -110,11 +129,11 @@ object ClockSettingsBinder {
when (tab) {
ClockSettingsViewModel.Tab.COLOR -> {
colorOptionContainer.isVisible = true
- sizeOptions.isInvisible = true
+ clockSizeRadioGroup.isInvisible = true
}
ClockSettingsViewModel.Tab.SIZE -> {
colorOptionContainer.isInvisible = true
- sizeOptions.isVisible = true
+ clockSizeRadioGroup.isVisible = true
}
}
}
@@ -122,6 +141,7 @@ object ClockSettingsBinder {
launch {
viewModel.colorOptions.collect { colorOptions ->
+ colorOptionContainerListView.removeAllViews()
colorOptions.forEachIndexed { index, colorOption ->
colorOption.payload?.let { payload ->
val item =
@@ -189,16 +209,28 @@ object ClockSettingsBinder {
clockHostView.addView(clockView)
when (size) {
ClockSize.DYNAMIC -> {
- sizeOptions.radioButtonDynamic.isChecked = true
- sizeOptions.radioButtonSmall.isChecked = false
+ // When clock size data flow emits clock size signal, we want
+ // to update the view without triggering on checked change,
+ // which is supposed to be triggered by user interaction only.
+ clockSizeRadioGroup.setOnCheckedChangeListener(null)
+ clockSizeRadioGroup.check(R.id.radio_dynamic)
+ clockSizeRadioGroup.setOnCheckedChangeListener(
+ onCheckedChangeListener
+ )
clockHostView.doOnPreDraw {
it.pivotX = it.width / 2F
it.pivotY = it.height / 2F
}
}
ClockSize.SMALL -> {
- sizeOptions.radioButtonDynamic.isChecked = false
- sizeOptions.radioButtonSmall.isChecked = true
+ // When clock size data flow emits clock size signal, we want
+ // to update the view without triggering on checked change,
+ // which is supposed to be triggered by user interaction only.
+ clockSizeRadioGroup.setOnCheckedChangeListener(null)
+ clockSizeRadioGroup.check(R.id.radio_small)
+ clockSizeRadioGroup.setOnCheckedChangeListener(
+ onCheckedChangeListener
+ )
clockHostView.doOnPreDraw {
it.pivotX = ClockCarouselView.getCenteredHostViewPivotX(it)
it.pivotY = 0F
@@ -238,4 +270,25 @@ object ClockSettingsBinder {
}
)
}
+
+ private fun getRadioText(
+ context: Context,
+ title: String,
+ description: String
+ ): SpannableString {
+ val text = SpannableString(title + "\n" + description)
+ text.setSpan(
+ TextAppearanceSpan(context, R.style.SectionTitleTextStyle),
+ 0,
+ title.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ text.setSpan(
+ TextAppearanceSpan(context, R.style.SectionSubtitleTextStyle),
+ title.length + 1,
+ title.length + 1 + description.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ return text
+ }
}
diff --git a/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt b/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt
index 4805f376..dc70633e 100644
--- a/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt
+++ b/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt
@@ -68,7 +68,7 @@ class ClockSettingsFragment : AppbarFragment() {
val injector = InjectorProvider.getInjector() as ThemePickerInjector
val lockScreenView: CardView = view.requireViewById(R.id.lock_preview)
- val colorViewModel = injector.getWallpaperColorsViewModel()
+ val wallpaperColorsRepository = injector.getWallpaperColorsRepository()
val displayUtils = injector.getDisplayUtils(context)
ScreenPreviewBinder.bind(
activity = activity,
@@ -88,18 +88,18 @@ class ClockSettingsFragment : AppbarFragment() {
injector
.getCurrentWallpaperInfoFactory(context)
.createCurrentWallpaperInfos(
- { homeWallpaper, lockWallpaper, _ ->
- continuation.resume(
- lockWallpaper ?: homeWallpaper,
- null,
- )
- },
+ context,
forceReload,
- )
+ ) { homeWallpaper, lockWallpaper, _ ->
+ continuation.resume(
+ lockWallpaper ?: homeWallpaper,
+ null,
+ )
+ }
}
},
onWallpaperColorChanged = { colors ->
- colorViewModel.setLockWallpaperColors(colors)
+ wallpaperColorsRepository.setLockWallpaperColors(colors)
},
initialExtrasProvider = {
Bundle().apply {
@@ -125,7 +125,7 @@ class ClockSettingsFragment : AppbarFragment() {
this,
injector.getClockSettingsViewModelFactory(
context,
- injector.getWallpaperColorsViewModel(),
+ injector.getWallpaperColorsRepository(),
injector.getClockViewFactory(activity),
),
)
diff --git a/src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt b/src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt
deleted file mode 100644
index b47c2433..00000000
--- a/src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.customization.picker.clock.ui.section
-
-import android.content.Context
-import android.view.LayoutInflater
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import com.android.customization.picker.clock.ui.binder.ClockSectionViewBinder
-import com.android.customization.picker.clock.ui.fragment.ClockSettingsFragment
-import com.android.customization.picker.clock.ui.view.ClockSectionView
-import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
-import com.android.wallpaper.R
-import com.android.wallpaper.config.BaseFlags
-import com.android.wallpaper.model.CustomizationSectionController
-import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController
-import kotlinx.coroutines.launch
-
-/** A [CustomizationSectionController] for clock customization. */
-class ClockSectionController(
- private val navigationController: CustomizationSectionNavigationController,
- private val lifecycleOwner: LifecycleOwner,
- private val flag: BaseFlags,
- private val viewModel: ClockSectionViewModel,
-) : CustomizationSectionController<ClockSectionView> {
-
- override fun isAvailable(context: Context): Boolean {
- return flag.isCustomClocksEnabled(context!!)
- }
-
- override fun createView(context: Context): ClockSectionView {
- val view =
- LayoutInflater.from(context)
- .inflate(
- R.layout.clock_section_view,
- null,
- ) as ClockSectionView
- lifecycleOwner.lifecycleScope.launch {
- ClockSectionViewBinder.bind(
- view = view,
- viewModel = viewModel,
- lifecycleOwner = lifecycleOwner
- ) {
- navigationController.navigateTo(ClockSettingsFragment())
- }
- }
- return view
- }
-}
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
index d4f501b7..cae4e06b 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
@@ -31,7 +31,7 @@ import androidx.core.view.get
import androidx.core.view.isNotEmpty
import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselItemViewModel
-import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.clocks.ClockController
import com.android.wallpaper.R
import com.android.wallpaper.picker.FixedWidthDisplayRatioFrameLayout
import java.lang.Float.max
@@ -384,7 +384,7 @@ class ClockCarouselView(
) : Carousel.Adapter {
fun getContentDescription(index: Int, resources: Resources): String {
- return clocks[index].getContentDescription(resources)
+ return clocks[index].contentDescription
}
override fun count(): Int {
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt b/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt
deleted file mode 100644
index 909491a3..00000000
--- a/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2023 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.clock.ui.view
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.FrameLayout
-import android.widget.RadioButton
-import com.android.customization.picker.clock.shared.ClockSize
-import com.android.wallpaper.R
-
-/** The radio button group to pick the clock size. */
-class ClockSizeRadioButtonGroup(
- context: Context,
- attrs: AttributeSet?,
-) : FrameLayout(context, attrs) {
-
- interface OnRadioButtonClickListener {
- fun onClick(size: ClockSize)
- }
-
- val radioButtonDynamic: RadioButton
- val radioButtonSmall: RadioButton
- var onRadioButtonClickListener: OnRadioButtonClickListener? = null
-
- init {
- LayoutInflater.from(context).inflate(R.layout.clock_size_radio_button_group, this, true)
- radioButtonDynamic = requireViewById(R.id.radio_button_dynamic)
- val buttonDynamic = requireViewById<View>(R.id.button_container_dynamic)
- buttonDynamic.setOnClickListener { onRadioButtonClickListener?.onClick(ClockSize.DYNAMIC) }
- radioButtonSmall = requireViewById(R.id.radio_button_large)
- val buttonLarge = requireViewById<View>(R.id.button_container_small)
- buttonLarge.setOnClickListener { onRadioButtonClickListener?.onClick(ClockSize.SMALL) }
- }
-}
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
index 3f6f423f..2ab162d3 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
@@ -15,226 +15,38 @@
*/
package com.android.customization.picker.clock.ui.view
-import android.app.WallpaperColors
-import android.app.WallpaperManager
-import android.content.Context
-import android.content.res.Resources
-import android.graphics.Point
-import android.graphics.Rect
import android.view.View
-import android.widget.FrameLayout
import androidx.annotation.ColorInt
-import androidx.core.text.util.LocalePreferences
import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.WeatherData
-import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.wallpaper.R
-import com.android.wallpaper.util.TimeUtils.TimeTicker
-import java.util.concurrent.ConcurrentHashMap
+import com.android.systemui.plugins.clocks.ClockController
-/**
- * Provide reusable clock view and related util functions.
- *
- * @property screenSize The Activity or Fragment's window size.
- */
-class ClockViewFactory(
- private val appContext: Context,
- val screenSize: Point,
- private val wallpaperManager: WallpaperManager,
- private val registry: ClockRegistry,
-) {
- private val resources = appContext.resources
- private val timeTickListeners: ConcurrentHashMap<Int, TimeTicker> = ConcurrentHashMap()
- private val clockControllers: HashMap<String, ClockController> = HashMap()
- private val smallClockFrames: HashMap<String, FrameLayout> = HashMap()
+interface ClockViewFactory {
- fun getController(clockId: String): ClockController {
- return clockControllers[clockId]
- ?: initClockController(clockId).also { clockControllers[clockId] = it }
- }
+ fun getController(clockId: String): ClockController
/**
* Reset the large view to its initial state when getting the view. This is because some view
* configs, e.g. animation state, might change during the reuse of the clock view in the app.
*/
- fun getLargeView(clockId: String): View {
- return getController(clockId).largeClock.let {
- it.animations.onPickerCarouselSwiping(1F)
- it.view
- }
- }
+ fun getLargeView(clockId: String): View
/**
* Reset the small view to its initial state when getting the view. This is because some view
* configs, e.g. translation X, might change during the reuse of the clock view in the app.
*/
- fun getSmallView(clockId: String): View {
- val smallClockFrame =
- smallClockFrames[clockId]
- ?: createSmallClockFrame().also {
- it.addView(getController(clockId).smallClock.view)
- smallClockFrames[clockId] = it
- }
- smallClockFrame.translationX = 0F
- smallClockFrame.translationY = 0F
- return smallClockFrame
- }
-
- private fun createSmallClockFrame(): FrameLayout {
- val smallClockFrame = FrameLayout(appContext)
- val layoutParams =
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.WRAP_CONTENT,
- resources.getDimensionPixelSize(R.dimen.small_clock_height)
- )
- layoutParams.topMargin = getSmallClockTopMargin()
- layoutParams.marginStart = getSmallClockStartPadding()
- smallClockFrame.layoutParams = layoutParams
- smallClockFrame.clipChildren = false
- return smallClockFrame
- }
-
- private fun getSmallClockTopMargin() =
- getStatusBarHeight(appContext.resources) +
- appContext.resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
-
- private fun getSmallClockStartPadding() =
- appContext.resources.getDimensionPixelSize(R.dimen.clock_padding_start)
-
- fun updateColorForAllClocks(@ColorInt seedColor: Int?) {
- clockControllers.values.forEach { it.events.onSeedColorChanged(seedColor = seedColor) }
- }
-
- fun updateColor(clockId: String, @ColorInt seedColor: Int?) {
- clockControllers[clockId]?.events?.onSeedColorChanged(seedColor)
- }
-
- fun updateRegionDarkness() {
- val isRegionDark = isLockscreenWallpaperDark()
- clockControllers.values.forEach {
- it.largeClock.events.onRegionDarknessChanged(isRegionDark)
- it.smallClock.events.onRegionDarknessChanged(isRegionDark)
- }
- }
-
- private fun isLockscreenWallpaperDark(): Boolean {
- val colors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK)
- return (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
- }
-
- fun updateTimeFormat(clockId: String) {
- getController(clockId)
- .events
- .onTimeFormatChanged(android.text.format.DateFormat.is24HourFormat(appContext))
- }
+ fun getSmallView(clockId: String): View
- fun registerTimeTicker(owner: LifecycleOwner) {
- val hashCode = owner.hashCode()
- if (timeTickListeners.keys.contains(hashCode)) {
- return
- }
+ fun updateColorForAllClocks(@ColorInt seedColor: Int?)
- timeTickListeners[hashCode] = TimeTicker.registerNewReceiver(appContext) { onTimeTick() }
- }
+ fun updateColor(clockId: String, @ColorInt seedColor: Int?)
- fun onDestroy() {
- timeTickListeners.forEach { (_, timeTicker) -> appContext.unregisterReceiver(timeTicker) }
- timeTickListeners.clear()
- clockControllers.clear()
- smallClockFrames.clear()
- }
+ fun updateRegionDarkness()
- private fun onTimeTick() {
- clockControllers.values.forEach {
- it.largeClock.events.onTimeTick()
- it.smallClock.events.onTimeTick()
- }
- }
+ fun updateTimeFormat(clockId: String)
- fun unregisterTimeTicker(owner: LifecycleOwner) {
- val hashCode = owner.hashCode()
- timeTickListeners[hashCode]?.let {
- appContext.unregisterReceiver(it)
- timeTickListeners.remove(hashCode)
- }
- }
-
- private fun initClockController(clockId: String): ClockController {
- val controller =
- registry.createExampleClock(clockId).also { it?.initialize(resources, 0f, 0f) }
- checkNotNull(controller)
-
- val isWallpaperDark = isLockscreenWallpaperDark()
- // Initialize large clock
- controller.largeClock.events.onRegionDarknessChanged(isWallpaperDark)
- controller.largeClock.events.onFontSettingChanged(
- resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
- )
- controller.largeClock.events.onTargetRegionChanged(getLargeClockRegion())
-
- // Initialize small clock
- controller.smallClock.events.onRegionDarknessChanged(isWallpaperDark)
- controller.smallClock.events.onFontSettingChanged(
- resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
- )
- controller.smallClock.events.onTargetRegionChanged(getSmallClockRegion())
-
- // Use placeholder for weather clock preview in picker.
- // Use locale default temp unit since assistant default is not available in this context.
- val useCelsius =
- LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS
- controller.events.onWeatherDataChanged(
- WeatherData(
- description = DESCRIPTION_PLACEHODLER,
- state = WEATHERICON_PLACEHOLDER,
- temperature =
- if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER
- else TEMPERATURE_FAHRENHEIT_PLACEHOLDER,
- useCelsius = useCelsius,
- )
- )
- return controller
- }
-
- /**
- * Simulate the function of getLargeClockRegion in KeyguardClockSwitch so that we can get a
- * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale
- * and position the clock view
- */
- private fun getLargeClockRegion(): Rect {
- val largeClockTopMargin =
- resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
- val targetHeight = resources.getDimensionPixelSize(R.dimen.large_clock_text_size) * 2
- val top = (screenSize.y / 2 - targetHeight / 2 + largeClockTopMargin / 2)
- return Rect(0, top, screenSize.x, (top + targetHeight))
- }
-
- /**
- * Simulate the function of getSmallClockRegion in KeyguardClockSwitch so that we can get a
- * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale
- * and position the clock view
- */
- private fun getSmallClockRegion(): Rect {
- val topMargin = getSmallClockTopMargin()
- val targetHeight = resources.getDimensionPixelSize(R.dimen.small_clock_height)
- return Rect(getSmallClockStartPadding(), topMargin, screenSize.x, topMargin + targetHeight)
- }
+ fun registerTimeTicker(owner: LifecycleOwner)
- companion object {
- const val DESCRIPTION_PLACEHODLER = ""
- const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58
- const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21
- val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
- const val USE_CELSIUS_PLACEHODLER = false
+ fun onDestroy()
- private fun getStatusBarHeight(resource: Resources): Int {
- var result = 0
- val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android")
- if (resourceId > 0) {
- result = resource.getDimensionPixelSize(resourceId)
- }
- return result
- }
- }
+ fun unregisterTimeTicker(owner: LifecycleOwner)
}
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt b/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt
new file mode 100644
index 00000000..5caea58d
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/view/ClockViewFactoryImpl.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2023 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.clock.ui.view
+
+import android.app.WallpaperColors
+import android.app.WallpaperManager
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.Rect
+import android.view.View
+import android.widget.FrameLayout
+import androidx.annotation.ColorInt
+import androidx.core.text.util.LocalePreferences
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.WeatherData
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.wallpaper.R
+import com.android.wallpaper.util.TimeUtils.TimeTicker
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * Provide reusable clock view and related util functions.
+ *
+ * @property screenSize The Activity or Fragment's window size.
+ */
+class ClockViewFactoryImpl(
+ private val appContext: Context,
+ val screenSize: Point,
+ private val wallpaperManager: WallpaperManager,
+ private val registry: ClockRegistry,
+) : ClockViewFactory {
+ private val resources = appContext.resources
+ private val timeTickListeners: ConcurrentHashMap<Int, TimeTicker> = ConcurrentHashMap()
+ private val clockControllers: HashMap<String, ClockController> = HashMap()
+ private val smallClockFrames: HashMap<String, FrameLayout> = HashMap()
+
+ override fun getController(clockId: String): ClockController {
+ return clockControllers[clockId]
+ ?: initClockController(clockId).also { clockControllers[clockId] = it }
+ }
+
+ /**
+ * Reset the large view to its initial state when getting the view. This is because some view
+ * configs, e.g. animation state, might change during the reuse of the clock view in the app.
+ */
+ override fun getLargeView(clockId: String): View {
+ return getController(clockId).largeClock.let {
+ it.animations.onPickerCarouselSwiping(1F)
+ it.view
+ }
+ }
+
+ /**
+ * Reset the small view to its initial state when getting the view. This is because some view
+ * configs, e.g. translation X, might change during the reuse of the clock view in the app.
+ */
+ override fun getSmallView(clockId: String): View {
+ val smallClockFrame =
+ smallClockFrames[clockId]
+ ?: createSmallClockFrame().also {
+ it.addView(getController(clockId).smallClock.view)
+ smallClockFrames[clockId] = it
+ }
+ smallClockFrame.translationX = 0F
+ smallClockFrame.translationY = 0F
+ return smallClockFrame
+ }
+
+ private fun createSmallClockFrame(): FrameLayout {
+ val smallClockFrame = FrameLayout(appContext)
+ val layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ resources.getDimensionPixelSize(R.dimen.small_clock_height)
+ )
+ layoutParams.topMargin = getSmallClockTopMargin()
+ layoutParams.marginStart = getSmallClockStartPadding()
+ smallClockFrame.layoutParams = layoutParams
+ smallClockFrame.clipChildren = false
+ return smallClockFrame
+ }
+
+ private fun getSmallClockTopMargin() =
+ getStatusBarHeight(appContext.resources) +
+ appContext.resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
+
+ private fun getSmallClockStartPadding() =
+ appContext.resources.getDimensionPixelSize(R.dimen.clock_padding_start)
+
+ override fun updateColorForAllClocks(@ColorInt seedColor: Int?) {
+ clockControllers.values.forEach { it.events.onSeedColorChanged(seedColor = seedColor) }
+ }
+
+ override fun updateColor(clockId: String, @ColorInt seedColor: Int?) {
+ clockControllers[clockId]?.events?.onSeedColorChanged(seedColor)
+ }
+
+ override fun updateRegionDarkness() {
+ val isRegionDark = isLockscreenWallpaperDark()
+ clockControllers.values.forEach {
+ it.largeClock.events.onRegionDarknessChanged(isRegionDark)
+ it.smallClock.events.onRegionDarknessChanged(isRegionDark)
+ }
+ }
+
+ private fun isLockscreenWallpaperDark(): Boolean {
+ val colors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK)
+ return (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
+ }
+
+ override fun updateTimeFormat(clockId: String) {
+ getController(clockId)
+ .events
+ .onTimeFormatChanged(android.text.format.DateFormat.is24HourFormat(appContext))
+ }
+
+ override fun registerTimeTicker(owner: LifecycleOwner) {
+ val hashCode = owner.hashCode()
+ if (timeTickListeners.keys.contains(hashCode)) {
+ return
+ }
+
+ timeTickListeners[hashCode] = TimeTicker.registerNewReceiver(appContext) { onTimeTick() }
+ }
+
+ override fun onDestroy() {
+ timeTickListeners.forEach { (_, timeTicker) -> appContext.unregisterReceiver(timeTicker) }
+ timeTickListeners.clear()
+ clockControllers.clear()
+ smallClockFrames.clear()
+ }
+
+ private fun onTimeTick() {
+ clockControllers.values.forEach {
+ it.largeClock.events.onTimeTick()
+ it.smallClock.events.onTimeTick()
+ }
+ }
+
+ override fun unregisterTimeTicker(owner: LifecycleOwner) {
+ val hashCode = owner.hashCode()
+ timeTickListeners[hashCode]?.let {
+ appContext.unregisterReceiver(it)
+ timeTickListeners.remove(hashCode)
+ }
+ }
+
+ private fun initClockController(clockId: String): ClockController {
+ val controller =
+ registry.createExampleClock(clockId).also { it?.initialize(resources, 0f, 0f) }
+ checkNotNull(controller)
+
+ val isWallpaperDark = isLockscreenWallpaperDark()
+ // Initialize large clock
+ controller.largeClock.events.onRegionDarknessChanged(isWallpaperDark)
+ controller.largeClock.events.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
+ )
+ controller.largeClock.events.onTargetRegionChanged(getLargeClockRegion())
+
+ // Initialize small clock
+ controller.smallClock.events.onRegionDarknessChanged(isWallpaperDark)
+ controller.smallClock.events.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
+ )
+ controller.smallClock.events.onTargetRegionChanged(getSmallClockRegion())
+
+ // Use placeholder for weather clock preview in picker.
+ // Use locale default temp unit since assistant default is not available in this context.
+ val useCelsius =
+ LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS
+ controller.events.onWeatherDataChanged(
+ WeatherData(
+ description = DESCRIPTION_PLACEHODLER,
+ state = WEATHERICON_PLACEHOLDER,
+ temperature =
+ if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER
+ else TEMPERATURE_FAHRENHEIT_PLACEHOLDER,
+ useCelsius = useCelsius,
+ )
+ )
+ return controller
+ }
+
+ /**
+ * Simulate the function of getLargeClockRegion in KeyguardClockSwitch so that we can get a
+ * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale
+ * and position the clock view
+ */
+ private fun getLargeClockRegion(): Rect {
+ val largeClockTopMargin =
+ resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
+ val targetHeight = resources.getDimensionPixelSize(R.dimen.large_clock_text_size) * 2
+ val top = (screenSize.y / 2 - targetHeight / 2 + largeClockTopMargin / 2)
+ return Rect(0, top, screenSize.x, (top + targetHeight))
+ }
+
+ /**
+ * Simulate the function of getSmallClockRegion in KeyguardClockSwitch so that we can get a
+ * proper region corresponding to lock screen in picker and for onTargetRegionChanged to scale
+ * and position the clock view
+ */
+ private fun getSmallClockRegion(): Rect {
+ val topMargin = getSmallClockTopMargin()
+ val targetHeight = resources.getDimensionPixelSize(R.dimen.small_clock_height)
+ return Rect(getSmallClockStartPadding(), topMargin, screenSize.x, topMargin + targetHeight)
+ }
+
+ companion object {
+ const val DESCRIPTION_PLACEHODLER = ""
+ const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58
+ const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21
+ val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
+ const val USE_CELSIUS_PLACEHODLER = false
+
+ private fun getStatusBarHeight(resource: Resources): Int {
+ var result = 0
+ val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android")
+ if (resourceId > 0) {
+ result = resource.getDimensionPixelSize(resourceId)
+ }
+ return result
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt
index 98114260..e5ac953c 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt
@@ -15,20 +15,8 @@
*/
package com.android.customization.picker.clock.ui.viewmodel
-import android.content.res.Resources
-import com.android.customization.module.CustomizationInjector
-import com.android.wallpaper.R
-import com.android.wallpaper.module.InjectorProvider
-
-class ClockCarouselItemViewModel(val clockId: String, val isSelected: Boolean) {
-
- /** Description for accessibility purposes when a clock is selected. */
- fun getContentDescription(resources: Resources): String {
- val clockContent =
- (InjectorProvider.getInjector() as? CustomizationInjector)
- ?.getClockDescriptionUtils(resources)
- ?.getDescription(clockId)
- ?: ""
- return resources.getString(R.string.select_clock_action_description, clockContent)
- }
-}
+class ClockCarouselItemViewModel(
+ val clockId: String,
+ val isSelected: Boolean,
+ val contentDescription: String,
+)
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
index 27c25a20..3f6394be 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
@@ -15,12 +15,15 @@
*/
package com.android.customization.picker.clock.ui.viewmodel
+import android.content.res.Resources
import android.graphics.Color
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
+import com.android.customization.module.logging.ThemesUserEventLogger
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
import com.android.customization.picker.clock.shared.ClockSize
+import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.wallpaper.R
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,6 +46,9 @@ import kotlinx.coroutines.launch
class ClockCarouselViewModel(
private val interactor: ClockPickerInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
+ private val clockViewFactory: ClockViewFactory,
+ private val resources: Resources,
+ private val logger: ThemesUserEventLogger,
) : ViewModel() {
@OptIn(ExperimentalCoroutinesApi::class)
val allClocks: StateFlow<List<ClockCarouselItemViewModel>> =
@@ -50,7 +56,14 @@ class ClockCarouselViewModel(
.mapLatest { allClocks ->
// Delay to avoid the case that the full list of clocks is not initiated.
delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
- allClocks.map { ClockCarouselItemViewModel(it.clockId, it.isSelected) }
+ allClocks.map {
+ val contentDescription =
+ resources.getString(
+ R.string.select_clock_action_description,
+ clockViewFactory.getController(it.clockId).config.description
+ )
+ ClockCarouselItemViewModel(it.clockId, it.isSelected, contentDescription)
+ }
}
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
@@ -111,18 +124,27 @@ class ClockCarouselViewModel(
fun setSelectedClock(clockId: String) {
setSelectedClockJob?.cancel()
setSelectedClockJob =
- viewModelScope.launch(backgroundDispatcher) { interactor.setSelectedClock(clockId) }
+ viewModelScope.launch(backgroundDispatcher) {
+ interactor.setSelectedClock(clockId)
+ logger.logClockApplied(clockId)
+ }
}
class Factory(
private val interactor: ClockPickerInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
+ private val clockViewFactory: ClockViewFactory,
+ private val resources: Resources,
+ private val logger: ThemesUserEventLogger,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return ClockCarouselViewModel(
interactor = interactor,
backgroundDispatcher = backgroundDispatcher,
+ clockViewFactory = clockViewFactory,
+ resources = resources,
+ logger = logger,
)
as T
}
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt
deleted file mode 100644
index 8a655225..00000000
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2023 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.clock.ui.viewmodel
-
-import android.content.Context
-import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
-import com.android.customization.picker.clock.shared.ClockSize
-import com.android.wallpaper.R
-import java.util.Locale
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
-
-/** View model for the clock section view on the lockscreen customization surface. */
-class ClockSectionViewModel(context: Context, interactor: ClockPickerInteractor) {
- val appContext: Context = context.applicationContext
- val clockColorMap: Map<String, ClockColorViewModel> =
- ClockColorViewModel.getPresetColorMap(appContext.resources)
- val selectedClockColorAndSizeText: Flow<String> =
- combine(interactor.selectedColorId, interactor.selectedClockSize, ::Pair).map {
- (selectedColorId, selectedClockSize) ->
- val colorText =
- clockColorMap[selectedColorId]?.colorName
- ?: appContext.getString(R.string.default_theme_title)
- val sizeText =
- when (selectedClockSize) {
- ClockSize.SMALL -> appContext.getString(R.string.clock_size_small)
- ClockSize.DYNAMIC -> appContext.getString(R.string.clock_size_dynamic)
- }
- appContext
- .getString(R.string.clock_color_and_size_description, colorText, sizeText)
- .lowercase()
- .replaceFirstChar {
- if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
- }
- }
-}
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
index a498c716..d0e4f8fe 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
@@ -21,9 +21,12 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.customization.model.color.ColorOptionImpl
+import com.android.customization.module.logging.ThemesUserEventLogger
+import com.android.customization.module.logging.ThemesUserEventLogger.Companion.NULL_SEED_COLOR
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
import com.android.customization.picker.clock.shared.ClockSize
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
+import com.android.customization.picker.clock.shared.toClockSizeForLogging
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.shared.model.ColorOptionModel
import com.android.customization.picker.color.shared.model.ColorType
@@ -53,6 +56,7 @@ private constructor(
private val clockPickerInteractor: ClockPickerInteractor,
private val colorPickerInteractor: ColorPickerInteractor,
private val getIsReactiveToTone: (clockId: String?) -> Boolean,
+ private val logger: ThemesUserEventLogger,
) : ViewModel() {
enum class Tab {
@@ -106,15 +110,17 @@ private constructor(
suspend fun onSliderProgressStop(progress: Int) {
val selectedColorId = selectedColorId.value ?: return
val clockColorViewModel = colorMap[selectedColorId] ?: return
+ val seedColor =
+ blendColorWithTone(
+ color = clockColorViewModel.color,
+ colorTone = clockColorViewModel.getColorTone(progress),
+ )
clockPickerInteractor.setClockColor(
selectedColorId = selectedColorId,
colorToneProgress = progress,
- seedColor =
- blendColorWithTone(
- color = clockColorViewModel.color,
- colorTone = clockColorViewModel.getColorTone(progress),
- )
+ seedColor = seedColor,
)
+ logger.logClockColorApplied(seedColor)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -169,18 +175,20 @@ private constructor(
} else {
{
viewModelScope.launch {
+ val seedColor =
+ blendColorWithTone(
+ color = colorModel.color,
+ colorTone =
+ colorModel.getColorTone(
+ colorToneProgress,
+ ),
+ )
clockPickerInteractor.setClockColor(
selectedColorId = colorModel.colorId,
colorToneProgress = colorToneProgress,
- seedColor =
- blendColorWithTone(
- color = colorModel.color,
- colorTone =
- colorModel.getColorTone(
- colorToneProgress,
- ),
- ),
+ seedColor = seedColor,
)
+ logger.logClockColorApplied(seedColor)
}
}
}
@@ -244,6 +252,7 @@ private constructor(
ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
seedColor = null,
)
+ logger.logClockColorApplied(NULL_SEED_COLOR)
}
}
}
@@ -254,7 +263,10 @@ private constructor(
val selectedClockSize: Flow<ClockSize> = clockPickerInteractor.selectedClockSize
fun setClockSize(size: ClockSize) {
- viewModelScope.launch { clockPickerInteractor.setClockSize(size) }
+ viewModelScope.launch {
+ clockPickerInteractor.setClockSize(size)
+ logger.logClockSizeApplied(size.toClockSizeForLogging())
+ }
}
private val _selectedTabPosition = MutableStateFlow(Tab.COLOR)
@@ -304,6 +316,7 @@ private constructor(
private val context: Context,
private val clockPickerInteractor: ClockPickerInteractor,
private val colorPickerInteractor: ColorPickerInteractor,
+ private val logger: ThemesUserEventLogger,
private val getIsReactiveToTone: (clockId: String?) -> Boolean,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@@ -312,6 +325,7 @@ private constructor(
context = context,
clockPickerInteractor = clockPickerInteractor,
colorPickerInteractor = colorPickerInteractor,
+ logger = logger,
getIsReactiveToTone = getIsReactiveToTone,
)
as T
diff --git a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
index 6540ce06..942a8460 100644
--- a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
@@ -24,8 +24,8 @@ import com.android.customization.model.color.ColorOptionImpl
import com.android.customization.picker.color.shared.model.ColorOptionModel
import com.android.customization.picker.color.shared.model.ColorType
import com.android.systemui.monet.Style
-import com.android.wallpaper.model.WallpaperColorsModel
-import com.android.wallpaper.model.WallpaperColorsViewModel
+import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository
+import com.android.wallpaper.picker.customization.shared.model.WallpaperColorsModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -37,14 +37,14 @@ import kotlinx.coroutines.suspendCancellableCoroutine
// TODO (b/262924623): refactor to remove dependency on ColorCustomizationManager & ColorOption
// TODO (b/268203200): Create test for ColorPickerRepositoryImpl
class ColorPickerRepositoryImpl(
- wallpaperColorsViewModel: WallpaperColorsViewModel,
+ wallpaperColorsRepository: WallpaperColorsRepository,
private val colorManager: ColorCustomizationManager,
) : ColorPickerRepository {
private val homeWallpaperColors: StateFlow<WallpaperColorsModel?> =
- wallpaperColorsViewModel.homeWallpaperColors
+ wallpaperColorsRepository.homeWallpaperColors
private val lockWallpaperColors: StateFlow<WallpaperColorsModel?> =
- wallpaperColorsViewModel.lockWallpaperColors
+ wallpaperColorsRepository.lockWallpaperColors
private var selectedColorOption: MutableStateFlow<ColorOptionModel> =
MutableStateFlow(getCurrentColorOption())
@@ -78,7 +78,7 @@ class ColorPickerRepositoryImpl(
homeColorsLoaded.colors,
lockColorsLoaded.colors
)
- colorManager.fetchRevampedUIOptions(
+ colorManager.fetchOptions(
object : CustomizationManager.OptionsFetchedListener<ColorOption?> {
override fun onOptionsLoaded(options: MutableList<ColorOption?>?) {
val wallpaperColorOptions: MutableList<ColorOptionModel> =
diff --git a/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt b/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt
index bb2ef9d3..f35d934d 100644
--- a/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt
+++ b/src/com/android/customization/picker/color/data/repository/FakeColorPickerRepository.kt
@@ -19,10 +19,12 @@ package com.android.customization.picker.color.data.repository
import android.content.Context
import android.graphics.Color
import android.text.TextUtils
+import com.android.customization.model.ResourceConstants
import com.android.customization.model.color.ColorOptionImpl
import com.android.customization.model.color.ColorOptionsProvider
import com.android.customization.picker.color.shared.model.ColorOptionModel
import com.android.customization.picker.color.shared.model.ColorType
+import com.android.systemui.monet.Style
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -49,6 +51,53 @@ class FakeColorPickerRepository(private val context: Context) : ColorPickerRepos
}
fun setOptions(
+ wallpaperOptions: List<ColorOptionImpl>,
+ presetOptions: List<ColorOptionImpl>,
+ selectedColorOptionType: ColorType,
+ selectedColorOptionIndex: Int
+ ) {
+ _colorOptions.value =
+ mapOf(
+ ColorType.WALLPAPER_COLOR to
+ buildList {
+ for ((index, colorOption) in wallpaperOptions.withIndex()) {
+ val isSelected =
+ selectedColorOptionType == ColorType.WALLPAPER_COLOR &&
+ selectedColorOptionIndex == index
+ val colorOptionModel =
+ ColorOptionModel(
+ key = "${ColorType.WALLPAPER_COLOR}::$index",
+ colorOption = colorOption,
+ isSelected = isSelected
+ )
+ if (isSelected) {
+ selectedColorOption = colorOptionModel
+ }
+ add(colorOptionModel)
+ }
+ },
+ ColorType.PRESET_COLOR to
+ buildList {
+ for ((index, colorOption) in presetOptions.withIndex()) {
+ val isSelected =
+ selectedColorOptionType == ColorType.PRESET_COLOR &&
+ selectedColorOptionIndex == index
+ val colorOptionModel =
+ ColorOptionModel(
+ key = "${ColorType.PRESET_COLOR}::$index",
+ colorOption = colorOption,
+ isSelected = isSelected
+ )
+ if (isSelected) {
+ selectedColorOption = colorOptionModel
+ }
+ add(colorOptionModel)
+ }
+ },
+ )
+ }
+
+ fun setOptions(
numWallpaperOptions: Int,
numPresetOptions: Int,
selectedColorOptionType: ColorType,
@@ -111,6 +160,22 @@ class FakeColorPickerRepository(private val context: Context) : ColorPickerRepos
return builder.build()
}
+ fun buildPresetOption(style: Style, seedColor: String): ColorOptionImpl {
+ val builder = ColorOptionImpl.Builder()
+ builder.lightColors =
+ intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT)
+ builder.darkColors =
+ intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT)
+ builder.type = ColorType.PRESET_COLOR
+ builder.source = ColorOptionsProvider.COLOR_SOURCE_PRESET
+ builder.style = style
+ builder.title = "Preset"
+ builder
+ .addOverlayPackage("TEST_PACKAGE_TYPE", "preset_color")
+ .addOverlayPackage(ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE, seedColor)
+ return builder.build()
+ }
+
private fun buildWallpaperOption(index: Int): ColorOptionImpl {
val builder = ColorOptionImpl.Builder()
builder.lightColors =
@@ -127,6 +192,22 @@ class FakeColorPickerRepository(private val context: Context) : ColorPickerRepos
return builder.build()
}
+ fun buildWallpaperOption(source: String, style: Style, seedColor: String): ColorOptionImpl {
+ val builder = ColorOptionImpl.Builder()
+ builder.lightColors =
+ intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT)
+ builder.darkColors =
+ intArrayOf(Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT)
+ builder.type = ColorType.WALLPAPER_COLOR
+ builder.source = source
+ builder.style = style
+ builder.title = "Dynamic"
+ builder
+ .addOverlayPackage("TEST_PACKAGE_TYPE", "wallpaper_color")
+ .addOverlayPackage(ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE, seedColor)
+ return builder.build()
+ }
+
override suspend fun select(colorOptionModel: ColorOptionModel) {
val colorOptions = _colorOptions.value
val wallpaperColorOptions = colorOptions[ColorType.WALLPAPER_COLOR]!!
diff --git a/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt b/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
index 0f82f494..9838c317 100644
--- a/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
+++ b/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
@@ -62,7 +62,7 @@ object ColorPickerBinder {
colorTypeTabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP))
val colorOptionAdapter =
OptionItemAdapter(
- layoutResourceId = R.layout.color_option_2,
+ layoutResourceId = R.layout.color_option,
lifecycleOwner = lifecycleOwner,
bindIcon = { foregroundView: View, colorIcon: ColorOptionIconViewModel ->
val colorOptionIconView = foregroundView as? ColorOptionIconView
diff --git a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
index 4ef29d6e..2c006090 100644
--- a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
+++ b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
@@ -33,11 +33,11 @@ import com.android.customization.model.mode.DarkModeSectionController
import com.android.customization.module.ThemePickerInjector
import com.android.customization.picker.color.ui.binder.ColorPickerBinder
import com.android.wallpaper.R
-import com.android.wallpaper.model.WallpaperColorsModel
-import com.android.wallpaper.model.WallpaperColorsViewModel
import com.android.wallpaper.module.CustomizationSections
import com.android.wallpaper.module.InjectorProvider
import com.android.wallpaper.picker.AppbarFragment
+import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository
+import com.android.wallpaper.picker.customization.shared.model.WallpaperColorsModel
import com.android.wallpaper.picker.customization.ui.binder.ScreenPreviewBinder
import com.android.wallpaper.picker.customization.ui.viewmodel.ScreenPreviewViewModel
import com.android.wallpaper.util.DisplayUtils
@@ -76,7 +76,7 @@ class ColorPickerFragment : AppbarFragment() {
val homeScreenView: CardView = view.requireViewById(R.id.home_preview)
val wallpaperInfoFactory = injector.getCurrentWallpaperInfoFactory(requireContext())
val displayUtils: DisplayUtils = injector.getDisplayUtils(requireContext())
- val wcViewModel = injector.getWallpaperColorsViewModel()
+ val wallpaperColorsRepository = injector.getWallpaperColorsRepository()
val wallpaperManager = WallpaperManager.getInstance(requireContext())
binding =
@@ -87,7 +87,7 @@ class ColorPickerFragment : AppbarFragment() {
requireActivity(),
injector.getColorPickerViewModelFactory(
context = requireContext(),
- wallpaperColorsViewModel = wcViewModel,
+ wallpaperColorsRepository = wallpaperColorsRepository,
),
)
.get(),
@@ -114,27 +114,27 @@ class ColorPickerFragment : AppbarFragment() {
wallpaperInfoProvider = { forceReload ->
suspendCancellableCoroutine { continuation ->
wallpaperInfoFactory.createCurrentWallpaperInfos(
- { homeWallpaper, lockWallpaper, _ ->
- lifecycleScope.launch {
- if (
- wcViewModel.lockWallpaperColors.value
- is WallpaperColorsModel.Loading
- ) {
- loadInitialColors(
- wallpaperManager,
- wcViewModel,
- CustomizationSections.Screen.LOCK_SCREEN
- )
- }
- }
- continuation.resume(lockWallpaper ?: homeWallpaper, null)
- },
+ context,
forceReload,
- )
+ ) { homeWallpaper, lockWallpaper, _ ->
+ lifecycleScope.launch {
+ if (
+ wallpaperColorsRepository.lockWallpaperColors.value
+ is WallpaperColorsModel.Loading
+ ) {
+ loadInitialColors(
+ wallpaperManager,
+ wallpaperColorsRepository,
+ CustomizationSections.Screen.LOCK_SCREEN
+ )
+ }
+ }
+ continuation.resume(lockWallpaper ?: homeWallpaper, null)
+ }
}
},
onWallpaperColorChanged = { colors ->
- wcViewModel.setLockWallpaperColors(colors)
+ wallpaperColorsRepository.setLockWallpaperColors(colors)
},
wallpaperInteractor = injector.getWallpaperInteractor(requireContext()),
screen = CustomizationSections.Screen.LOCK_SCREEN,
@@ -165,27 +165,27 @@ class ColorPickerFragment : AppbarFragment() {
wallpaperInfoProvider = { forceReload ->
suspendCancellableCoroutine { continuation ->
wallpaperInfoFactory.createCurrentWallpaperInfos(
- { homeWallpaper, lockWallpaper, _ ->
- lifecycleScope.launch {
- if (
- wcViewModel.homeWallpaperColors.value
- is WallpaperColorsModel.Loading
- ) {
- loadInitialColors(
- wallpaperManager,
- wcViewModel,
- CustomizationSections.Screen.HOME_SCREEN
- )
- }
- }
- continuation.resume(homeWallpaper ?: lockWallpaper, null)
- },
+ context,
forceReload,
- )
+ ) { homeWallpaper, lockWallpaper, _ ->
+ lifecycleScope.launch {
+ if (
+ wallpaperColorsRepository.homeWallpaperColors.value
+ is WallpaperColorsModel.Loading
+ ) {
+ loadInitialColors(
+ wallpaperManager,
+ wallpaperColorsRepository,
+ CustomizationSections.Screen.HOME_SCREEN
+ )
+ }
+ }
+ continuation.resume(homeWallpaper ?: lockWallpaper, null)
+ }
}
},
onWallpaperColorChanged = { colors ->
- wcViewModel.setHomeWallpaperColors(colors)
+ wallpaperColorsRepository.setHomeWallpaperColors(colors)
},
wallpaperInteractor = injector.getWallpaperInteractor(requireContext()),
screen = CustomizationSections.Screen.HOME_SCREEN,
@@ -202,7 +202,8 @@ class ColorPickerFragment : AppbarFragment() {
DarkModeSectionController(
context,
lifecycle,
- injector.getDarkModeSnapshotRestorer(requireContext())
+ injector.getDarkModeSnapshotRestorer(requireContext()),
+ injector.getUserEventLogger(requireContext()),
)
.createView(requireContext())
darkModeSectionView.background = null
@@ -218,7 +219,7 @@ class ColorPickerFragment : AppbarFragment() {
private suspend fun loadInitialColors(
wallpaperManager: WallpaperManager,
- colorViewModel: WallpaperColorsViewModel,
+ colorViewModel: WallpaperColorsRepository,
screen: CustomizationSections.Screen,
) {
withContext(Dispatchers.IO) {
diff --git a/src/com/android/customization/picker/color/ui/section/ColorSectionController2.kt b/src/com/android/customization/picker/color/ui/section/ColorSectionController.kt
index f1c982b4..a36fd80a 100644
--- a/src/com/android/customization/picker/color/ui/section/ColorSectionController2.kt
+++ b/src/com/android/customization/picker/color/ui/section/ColorSectionController.kt
@@ -22,37 +22,37 @@ import android.view.LayoutInflater
import androidx.lifecycle.LifecycleOwner
import com.android.customization.picker.color.ui.binder.ColorSectionViewBinder
import com.android.customization.picker.color.ui.fragment.ColorPickerFragment
-import com.android.customization.picker.color.ui.view.ColorSectionView2
+import com.android.customization.picker.color.ui.view.ColorSectionView
import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
import com.android.wallpaper.R
import com.android.wallpaper.model.CustomizationSectionController
import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController as NavigationController
-class ColorSectionController2(
+class ColorSectionController(
private val navigationController: NavigationController,
private val viewModel: ColorPickerViewModel,
private val lifecycleOwner: LifecycleOwner
-) : CustomizationSectionController<ColorSectionView2> {
+) : CustomizationSectionController<ColorSectionView> {
override fun isAvailable(context: Context): Boolean {
return true
}
- override fun createView(context: Context): ColorSectionView2 {
+ override fun createView(context: Context): ColorSectionView {
return createView(context, CustomizationSectionController.ViewCreationParams())
}
override fun createView(
context: Context,
params: CustomizationSectionController.ViewCreationParams
- ): ColorSectionView2 {
+ ): ColorSectionView {
@SuppressWarnings("It is fine to inflate with null parent for our need.")
val view =
LayoutInflater.from(context)
.inflate(
- R.layout.color_section_view2,
+ R.layout.color_section_view,
null,
- ) as ColorSectionView2
+ ) as ColorSectionView
ColorSectionViewBinder.bind(
view = view,
viewModel = viewModel,
diff --git a/src/com/android/customization/picker/color/ui/view/ColorSectionView2.kt b/src/com/android/customization/picker/color/ui/view/ColorSectionView.kt
index 7a8f21af..a89292d8 100644
--- a/src/com/android/customization/picker/color/ui/view/ColorSectionView2.kt
+++ b/src/com/android/customization/picker/color/ui/view/ColorSectionView.kt
@@ -23,4 +23,4 @@ import com.android.wallpaper.picker.SectionView
* The class inherits from {@link SectionView} as the view representing the color section of the
* customization picker. It displays a list of color options and an overflow option.
*/
-class ColorSectionView2(context: Context, attrs: AttributeSet?) : SectionView(context, attrs)
+class ColorSectionView(context: Context, attrs: AttributeSet?) : SectionView(context, attrs)
diff --git a/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt b/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt
index 67c68387..ed83136e 100644
--- a/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt
+++ b/src/com/android/customization/picker/color/ui/viewmodel/ColorPickerViewModel.kt
@@ -21,6 +21,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.customization.model.color.ColorOptionImpl
+import com.android.customization.module.logging.ThemesUserEventLogger
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.color.shared.model.ColorType
import com.android.wallpaper.R
@@ -43,6 +44,7 @@ class ColorPickerViewModel
private constructor(
context: Context,
private val interactor: ColorPickerInteractor,
+ private val logger: ThemesUserEventLogger,
) : ViewModel() {
private val selectedColorTypeTabId = MutableStateFlow<ColorType?>(null)
@@ -142,6 +144,14 @@ private constructor(
{
viewModelScope.launch {
interactor.select(colorOptionModel)
+ logger.logThemeColorApplied(
+ colorOptionModel.colorOption
+ .sourceForLogging,
+ colorOptionModel.colorOption
+ .styleForLogging,
+ colorOptionModel.colorOption
+ .seedColorForLogging,
+ )
}
}
}
@@ -205,12 +215,14 @@ private constructor(
class Factory(
private val context: Context,
private val interactor: ColorPickerInteractor,
+ private val logger: ThemesUserEventLogger,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return ColorPickerViewModel(
context = context,
interactor = interactor,
+ logger = logger,
)
as T
}
diff --git a/src/com/android/customization/picker/grid/GridFragment.java b/src/com/android/customization/picker/grid/GridFragment.java
deleted file mode 100644
index 4de1dab7..00000000
--- a/src/com/android/customization/picker/grid/GridFragment.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2018 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.grid;
-
-import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY_TEXT;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.constraintlayout.widget.ConstraintLayout;
-import androidx.constraintlayout.widget.ConstraintSet;
-import androidx.core.widget.ContentLoadingProgressBar;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.customization.model.CustomizationManager.Callback;
-import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
-import com.android.customization.model.CustomizationOption;
-import com.android.customization.model.grid.GridOption;
-import com.android.customization.model.grid.GridOptionViewModel;
-import com.android.customization.model.grid.GridOptionsManager;
-import com.android.customization.module.ThemesUserEventLogger;
-import com.android.customization.picker.WallpaperPreviewer;
-import com.android.customization.widget.OptionSelectorController;
-import com.android.customization.widget.OptionSelectorController.CheckmarkStyle;
-import com.android.wallpaper.R;
-import com.android.wallpaper.model.WallpaperInfo;
-import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
-import com.android.wallpaper.module.InjectorProvider;
-import com.android.wallpaper.picker.AppbarFragment;
-import com.android.wallpaper.util.LaunchUtils;
-import com.android.wallpaper.util.ScreenSizeCalculator;
-import com.android.wallpaper.widget.BottomActionBar;
-
-import com.bumptech.glide.Glide;
-
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Fragment that contains the UI for selecting and applying a GridOption.
- */
-public class GridFragment extends AppbarFragment {
-
- private static final String TAG = "GridFragment";
-
- private WallpaperInfo mHomeWallpaper;
- private RecyclerView mOptionsContainer;
- private OptionSelectorController<GridOption> mOptionsController;
- private GridOptionsManager mGridManager;
- private ContentLoadingProgressBar mLoading;
- private ConstraintLayout mContent;
- private View mError;
- private BottomActionBar mBottomActionBar;
- private ThemesUserEventLogger mEventLogger;
- private GridOptionPreviewer mGridOptionPreviewer;
- private GridOptionViewModel mGridOptionViewModel;
-
- private final Callback mApplyGridCallback = new Callback() {
- @Override
- public void onSuccess() {
- mGridManager.fetchOptions(unused -> {}, true);
- Toast.makeText(getContext(), R.string.applied_grid_msg, Toast.LENGTH_SHORT).show();
- getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
- getActivity().finish();
-
- // Go back to launcher home
- LaunchUtils.launchHome(getContext());
- }
-
- @Override
- public void onError(@Nullable Throwable throwable) {
- // Since we disabled it when clicked apply button.
- mBottomActionBar.enableActions();
- mBottomActionBar.hide();
- mGridOptionViewModel.setBottomActionBarVisible(false);
- //TODO(chihhangchuang): handle
- }
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mGridOptionViewModel = new ViewModelProvider(requireActivity()).get(
- GridOptionViewModel.class);
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(
- R.layout.fragment_grid_picker, container, /* attachToRoot */ false);
- setUpToolbar(view);
- mContent = view.findViewById(R.id.content_section);
- mOptionsContainer = view.findViewById(R.id.options_container);
- AccessibilityManager accessibilityManager =
- (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
- if (accessibilityManager.isEnabled()) {
- // Make Talkback focus won't reset when notifyDataSetChange
- mOptionsContainer.setItemAnimator(null);
- }
-
- // Set aspect ratio on the preview card dynamically.
- Point mScreenSize;
- ScreenSizeCalculator screenSizeCalculator = ScreenSizeCalculator.getInstance();
- mScreenSize = screenSizeCalculator.getScreenSize(
- requireActivity().getWindowManager().getDefaultDisplay());
- ConstraintSet set = new ConstraintSet();
- set.clone(mContent);
- String ratio = String.format(Locale.US, "%d:%d", mScreenSize.x, mScreenSize.y);
- set.setDimensionRatio(R.id.preview_card_container, ratio);
- set.applyTo(mContent);
-
- mLoading = view.findViewById(R.id.loading_indicator);
- mError = view.findViewById(R.id.error_section);
-
- // For nav bar edge-to-edge effect.
- view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
- v.setPadding(
- v.getPaddingLeft(),
- windowInsets.getSystemWindowInsetTop(),
- v.getPaddingRight(),
- windowInsets.getSystemWindowInsetBottom());
- return windowInsets.consumeSystemWindowInsets();
- });
-
- // Clear memory cache whenever grid fragment view is being loaded.
- Glide.get(getContext()).clearMemory();
-
- mGridManager = GridOptionsManager.getInstance(getContext());
- mEventLogger = (ThemesUserEventLogger) InjectorProvider.getInjector()
- .getUserEventLogger(getContext());
- setUpOptions();
-
- SurfaceView wallpaperSurface = view.findViewById(R.id.wallpaper_preview_surface);
- WallpaperPreviewer wallpaperPreviewer = new WallpaperPreviewer(getLifecycle(),
- getActivity(), view.findViewById(R.id.wallpaper_preview_image), wallpaperSurface,
- view.findViewById(R.id.grid_fadein_scrim));
- // Loads current Wallpaper.
- CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector()
- .getCurrentWallpaperInfoFactory(getContext().getApplicationContext());
- factory.createCurrentWallpaperInfos((homeWallpaper, lockWallpaper, presentationMode) -> {
- mHomeWallpaper = homeWallpaper;
- wallpaperPreviewer.setWallpaper(mHomeWallpaper, /* listener= */ null);
- }, false);
-
- mGridOptionPreviewer = new GridOptionPreviewer(mGridManager,
- view.findViewById(R.id.grid_preview_container));
-
- return view;
- }
-
- @Override
- public boolean onBackPressed() {
- mGridOptionViewModel.setSelectedOption(null);
- mGridOptionViewModel.setBottomActionBarVisible(false);
- return super.onBackPressed();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mGridOptionPreviewer != null) {
- mGridOptionPreviewer.release();
- }
- }
-
- @Override
- public CharSequence getDefaultTitle() {
- return getString(R.string.grid_title);
- }
-
- @Override
- protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
- super.onBottomActionBarReady(bottomActionBar);
- mBottomActionBar = bottomActionBar;
- mBottomActionBar.showActionsOnly(APPLY_TEXT);
- mBottomActionBar.setActionClickListener(APPLY_TEXT,
- v -> applyGridOption(mGridOptionViewModel.getSelectedOption()));
- mBottomActionBar.setActionAccessibilityTraversalAfter(APPLY_TEXT,
- mOptionsContainer.getId());
- }
-
- private void applyGridOption(GridOption gridOption) {
- mBottomActionBar.disableActions();
- mGridManager.apply(gridOption, mApplyGridCallback);
- }
-
- private void setUpOptions() {
- hideError();
- mLoading.show();
- mGridManager.fetchOptions(new OptionsFetchedListener<GridOption>() {
- @Override
- public void onOptionsLoaded(List<GridOption> options) {
- mLoading.hide();
- mOptionsController = new OptionSelectorController<>(
- mOptionsContainer, options, /* useGrid= */ false,
- CheckmarkStyle.CENTER_CHANGE_COLOR_WHEN_NOT_SELECTED);
- mOptionsController.initOptions(mGridManager);
- GridOption previouslySelectedOption = findEquivalent(options,
- mGridOptionViewModel.getSelectedOption());
- mGridOptionViewModel.setSelectedOption(
- previouslySelectedOption != null
- ? previouslySelectedOption
- : getActiveOption(options));
-
- mOptionsController.setSelectedOption(mGridOptionViewModel.getSelectedOption());
- onOptionSelected(mGridOptionViewModel.getSelectedOption());
- restoreBottomActionBarVisibility();
-
- mOptionsController.addListener(selectedOption -> {
- String title = selectedOption.getTitle();
- int stringId = R.string.option_previewed_description;
- if (selectedOption.isActive(mGridManager)) {
- stringId = R.string.option_applied_previewed_description;
- }
- CharSequence cd = getContext().getString(stringId, title);
- mOptionsContainer.announceForAccessibility(cd);
- onOptionSelected(selectedOption);
- mBottomActionBar.show();
- mGridOptionViewModel.setBottomActionBarVisible(true);
- });
- }
-
- @Override
- public void onError(@Nullable Throwable throwable) {
- if (throwable != null) {
- Log.e(TAG, "Error loading grid options", throwable);
- }
- showError();
- }
- }, /*reload= */ true);
- }
-
- private GridOption getActiveOption(List<GridOption> options) {
- return options.stream()
- .filter(option -> option.isActive(mGridManager))
- .findAny()
- // For development only, as there should always be a grid set.
- .orElse(options.get(0));
- }
-
- @Nullable
- private GridOption findEquivalent(List<GridOption> options, GridOption target) {
- return options.stream()
- .filter(option -> option.equals(target))
- .findAny()
- .orElse(null);
- }
-
- private void hideError() {
- mContent.setVisibility(View.VISIBLE);
- mError.setVisibility(View.GONE);
- }
-
- private void showError() {
- mLoading.hide();
- mContent.setVisibility(View.GONE);
- mError.setVisibility(View.VISIBLE);
- }
-
- private void onOptionSelected(CustomizationOption selectedOption) {
- mGridOptionViewModel.setSelectedOption((GridOption) selectedOption);
- mEventLogger.logGridSelected(mGridOptionViewModel.getSelectedOption());
- mGridOptionPreviewer.setGridOption(mGridOptionViewModel.getSelectedOption());
- }
-
- private void restoreBottomActionBarVisibility() {
- if (mGridOptionViewModel.getBottomActionBarVisible()) {
- mBottomActionBar.show();
- } else {
- mBottomActionBar.hide();
- }
- }
-}
diff --git a/src/com/android/customization/picker/grid/GridOptionPreviewer.java b/src/com/android/customization/picker/grid/GridOptionPreviewer.java
deleted file mode 100644
index 7786d35c..00000000
--- a/src/com/android/customization/picker/grid/GridOptionPreviewer.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2020 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.grid;
-
-import android.content.Context;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.ViewGroup;
-
-import com.android.customization.model.grid.GridOption;
-import com.android.customization.model.grid.GridOptionsManager;
-import com.android.wallpaper.R;
-import com.android.wallpaper.picker.WorkspaceSurfaceHolderCallback;
-import com.android.wallpaper.util.PreviewUtils;
-import com.android.wallpaper.util.SurfaceViewUtils;
-
-/** A class to load the {@link GridOption} preview to the view. */
-class GridOptionPreviewer {
-
- private final GridOptionsManager mGridManager;
- private final ViewGroup mPreviewContainer;
-
- private SurfaceView mGridOptionSurface;
- private GridOption mGridOption;
- private GridOptionSurfaceHolderCallback mSurfaceCallback;
-
- GridOptionPreviewer(GridOptionsManager gridManager, ViewGroup previewContainer) {
- mGridManager = gridManager;
- mPreviewContainer = previewContainer;
- }
-
- /** Loads the Grid option into the container view. */
- public void setGridOption(GridOption gridOption) {
- mGridOption = gridOption;
- if (mGridOption != null) {
- updateWorkspacePreview();
- }
- }
-
- /** Releases the view resource. */
- public void release() {
- if (mGridOptionSurface != null) {
- mSurfaceCallback.cleanUp();
- mGridOptionSurface = null;
- }
- mPreviewContainer.removeAllViews();
- }
-
- private void updateWorkspacePreview() {
- // Reattach SurfaceView to trigger #surfaceCreated to update preview for different option.
- mPreviewContainer.removeAllViews();
- if (mSurfaceCallback != null) {
- mSurfaceCallback.cleanUp();
- mSurfaceCallback.resetLastSurface();
- if (mGridOptionSurface != null) {
- mGridOptionSurface.getHolder().removeCallback(mSurfaceCallback);
- }
- }
- mGridOptionSurface = new SurfaceView(mPreviewContainer.getContext());
- mGridOptionSurface.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- mGridOptionSurface.setZOrderMediaOverlay(true);
- mSurfaceCallback = new GridOptionSurfaceHolderCallback(mGridOptionSurface,
- mGridOptionSurface.getContext());
- mGridOptionSurface.getHolder().addCallback(mSurfaceCallback);
- mPreviewContainer.addView(mGridOptionSurface);
- }
-
- private class GridOptionSurfaceHolderCallback extends WorkspaceSurfaceHolderCallback {
- private GridOptionSurfaceHolderCallback(SurfaceView workspaceSurface, Context context) {
- super(
- workspaceSurface,
- new PreviewUtils(
- context, context.getString(R.string.grid_control_metadata_name)));
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- if (mGridOption != null) {
- super.surfaceCreated(holder);
- }
- }
-
- @Override
- protected void requestPreview(SurfaceView workspaceSurface,
- PreviewUtils.WorkspacePreviewCallback callback) {
- mGridManager.renderPreview(
- SurfaceViewUtils.createSurfaceViewRequest(workspaceSurface),
- mGridOption.name, callback);
- }
- }
-}
diff --git a/src/com/android/customization/picker/grid/data/repository/GridRepository.kt b/src/com/android/customization/picker/grid/data/repository/GridRepository.kt
new file mode 100644
index 00000000..f3844294
--- /dev/null
+++ b/src/com/android/customization/picker/grid/data/repository/GridRepository.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 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.grid.data.repository
+
+import androidx.lifecycle.asFlow
+import com.android.customization.model.CustomizationManager
+import com.android.customization.model.CustomizationManager.Callback
+import com.android.customization.model.grid.GridOption
+import com.android.customization.model.grid.GridOptionsManager
+import com.android.customization.picker.grid.shared.model.GridOptionItemModel
+import com.android.customization.picker.grid.shared.model.GridOptionItemsModel
+import kotlin.coroutines.resume
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+
+interface GridRepository {
+ suspend fun isAvailable(): Boolean
+ fun getOptionChanges(): Flow<Unit>
+ suspend fun getOptions(): GridOptionItemsModel
+ fun getSelectedOption(): GridOption?
+ fun applySelectedOption(callback: Callback)
+ fun clearSelectedOption()
+ fun isSelectedOptionApplied(): Boolean
+}
+
+class GridRepositoryImpl(
+ private val applicationScope: CoroutineScope,
+ private val manager: GridOptionsManager,
+ private val backgroundDispatcher: CoroutineDispatcher,
+ private val isGridApplyButtonEnabled: Boolean,
+) : GridRepository {
+
+ override suspend fun isAvailable(): Boolean {
+ return withContext(backgroundDispatcher) { manager.isAvailable }
+ }
+
+ override fun getOptionChanges(): Flow<Unit> =
+ manager.getOptionChangeObservable(/* handler= */ null).asFlow().map {}
+
+ private val selectedOption = MutableStateFlow<GridOption?>(null)
+
+ private var appliedOption: GridOption? = null
+
+ override fun getSelectedOption() = selectedOption.value
+
+ override suspend fun getOptions(): GridOptionItemsModel {
+ return withContext(backgroundDispatcher) {
+ suspendCancellableCoroutine { continuation ->
+ manager.fetchOptions(
+ object : CustomizationManager.OptionsFetchedListener<GridOption> {
+ override fun onOptionsLoaded(options: MutableList<GridOption>?) {
+ val optionsOrEmpty = options ?: emptyList()
+ // After Apply Button is added, we will rely on onSelected() method
+ // to update selectedOption.
+ if (!isGridApplyButtonEnabled || selectedOption.value == null) {
+ selectedOption.value = optionsOrEmpty.find { it.isActive(manager) }
+ }
+ if (isGridApplyButtonEnabled && appliedOption == null) {
+ appliedOption = selectedOption.value
+ }
+ continuation.resume(
+ GridOptionItemsModel.Loaded(
+ optionsOrEmpty.map { option -> toModel(option) }
+ )
+ )
+ }
+
+ override fun onError(throwable: Throwable?) {
+ continuation.resume(
+ GridOptionItemsModel.Error(
+ throwable ?: Exception("Failed to load grid options!")
+ ),
+ )
+ }
+ },
+ /* reload= */ true,
+ )
+ }
+ }
+ }
+
+ private fun toModel(option: GridOption): GridOptionItemModel {
+ return GridOptionItemModel(
+ name = option.title,
+ rows = option.rows,
+ cols = option.cols,
+ isSelected =
+ selectedOption
+ .map { it.key() }
+ .map { selectedOptionKey -> option.key() == selectedOptionKey }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ ),
+ onSelected = { onSelected(option) },
+ )
+ }
+
+ private suspend fun onSelected(option: GridOption) {
+ withContext(backgroundDispatcher) {
+ suspendCancellableCoroutine { continuation ->
+ if (isGridApplyButtonEnabled) {
+ selectedOption.value?.setIsCurrent(false)
+ selectedOption.value = option
+ selectedOption.value?.setIsCurrent(true)
+ manager.preview(option)
+ continuation.resume(true)
+ } else {
+ manager.apply(
+ option,
+ object : CustomizationManager.Callback {
+ override fun onSuccess() {
+ continuation.resume(true)
+ }
+
+ override fun onError(throwable: Throwable?) {
+ continuation.resume(false)
+ }
+ },
+ )
+ }
+ }
+ }
+ }
+
+ override fun applySelectedOption(callback: Callback) {
+ val option = getSelectedOption()
+ manager.apply(
+ option,
+ if (isGridApplyButtonEnabled) {
+ object : Callback {
+ override fun onSuccess() {
+ callback.onSuccess()
+ appliedOption = option
+ }
+
+ override fun onError(throwable: Throwable?) {
+ callback.onError(throwable)
+ }
+ }
+ } else callback
+ )
+ }
+
+ override fun clearSelectedOption() {
+ if (!isGridApplyButtonEnabled) {
+ return
+ }
+ selectedOption.value?.setIsCurrent(false)
+ selectedOption.value = null
+ }
+
+ override fun isSelectedOptionApplied() = selectedOption.value?.name == appliedOption?.name
+
+ private fun GridOption?.key(): String? {
+ return if (this != null) "${cols}x${rows}" else null
+ }
+}
diff --git a/src/com/android/customization/picker/grid/domain/interactor/GridInteractor.kt b/src/com/android/customization/picker/grid/domain/interactor/GridInteractor.kt
new file mode 100644
index 00000000..02e16ddf
--- /dev/null
+++ b/src/com/android/customization/picker/grid/domain/interactor/GridInteractor.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 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.grid.domain.interactor
+
+import com.android.customization.model.CustomizationManager
+import com.android.customization.model.grid.GridOption
+import com.android.customization.picker.grid.data.repository.GridRepository
+import com.android.customization.picker.grid.shared.model.GridOptionItemModel
+import com.android.customization.picker.grid.shared.model.GridOptionItemsModel
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+class GridInteractor(
+ private val applicationScope: CoroutineScope,
+ private val repository: GridRepository,
+ private val snapshotRestorer: Provider<GridSnapshotRestorer>,
+) {
+ val options: Flow<GridOptionItemsModel> =
+ flow { emit(repository.isAvailable()) }
+ .flatMapLatest { isAvailable ->
+ if (isAvailable) {
+ // this upstream flow tells us each time the options are changed.
+ repository
+ .getOptionChanges()
+ // when we start, we pretend the options _just_ changed. This way, we load
+ // something as soon as possible into the flow so it's ready by the time the
+ // first observer starts to observe.
+ .onStart { emit(Unit) }
+ // each time the options changed, we load them.
+ .map { reload() }
+ // we place the loaded options in a SharedFlow so downstream observers all
+ // share the same flow and don't trigger a new one each time they want to
+ // start observing.
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
+ } else {
+ emptyFlow()
+ }
+ }
+
+ suspend fun setSelectedOption(model: GridOptionItemModel) {
+ model.onSelected.invoke()
+ }
+
+ suspend fun getSelectedOption(): GridOptionItemModel? {
+ return (repository.getOptions() as? GridOptionItemsModel.Loaded)?.options?.firstOrNull {
+ optionItem ->
+ optionItem.isSelected.value
+ }
+ }
+
+ fun getSelectOptionNonSuspend(): GridOption? = repository.getSelectedOption()
+
+ fun clearSelectedOption() = repository.clearSelectedOption()
+
+ fun isSelectedOptionApplied() = repository.isSelectedOptionApplied()
+
+ fun applySelectedOption(callback: CustomizationManager.Callback) {
+ repository.applySelectedOption(callback)
+ }
+
+ private suspend fun reload(): GridOptionItemsModel {
+ val model = repository.getOptions()
+ return if (model is GridOptionItemsModel.Loaded) {
+ GridOptionItemsModel.Loaded(
+ options =
+ model.options.map { option ->
+ GridOptionItemModel(
+ name = option.name,
+ cols = option.cols,
+ rows = option.rows,
+ isSelected = option.isSelected,
+ onSelected = {
+ option.onSelected()
+ snapshotRestorer.get().store(option)
+ },
+ )
+ }
+ )
+ } else {
+ model
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/grid/domain/interactor/GridSnapshotRestorer.kt b/src/com/android/customization/picker/grid/domain/interactor/GridSnapshotRestorer.kt
new file mode 100644
index 00000000..74d77f75
--- /dev/null
+++ b/src/com/android/customization/picker/grid/domain/interactor/GridSnapshotRestorer.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.grid.domain.interactor
+
+import android.util.Log
+import com.android.customization.picker.grid.shared.model.GridOptionItemModel
+import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
+import com.android.wallpaper.picker.undo.domain.interactor.SnapshotStore
+import com.android.wallpaper.picker.undo.shared.model.RestorableSnapshot
+
+class GridSnapshotRestorer(
+ private val interactor: GridInteractor,
+) : SnapshotRestorer {
+
+ private var store: SnapshotStore = SnapshotStore.NOOP
+ private var originalOption: GridOptionItemModel? = null
+
+ override suspend fun setUpSnapshotRestorer(store: SnapshotStore): RestorableSnapshot {
+ this.store = store
+ val option = interactor.getSelectedOption()
+ originalOption = option
+ return snapshot(option)
+ }
+
+ override suspend fun restoreToSnapshot(snapshot: RestorableSnapshot) {
+ val optionNameFromSnapshot = snapshot.args[KEY_GRID_OPTION_NAME]
+ originalOption?.let { optionToRestore ->
+ if (optionToRestore.name != optionNameFromSnapshot) {
+ Log.wtf(
+ TAG,
+ """Original snapshot name was ${optionToRestore.name} but we're being told to
+ | restore to $optionNameFromSnapshot. The current implementation doesn't
+ | support undo, only a reset back to the original grid option."""
+ .trimMargin(),
+ )
+ }
+
+ interactor.setSelectedOption(optionToRestore)
+ }
+ }
+
+ fun store(option: GridOptionItemModel) {
+ store.store(snapshot(option))
+ }
+
+ private fun snapshot(option: GridOptionItemModel?): RestorableSnapshot {
+ return RestorableSnapshot(
+ args =
+ buildMap {
+ option?.name?.let { optionName -> put(KEY_GRID_OPTION_NAME, optionName) }
+ }
+ )
+ }
+
+ companion object {
+ private const val TAG = "GridSnapshotRestorer"
+ private const val KEY_GRID_OPTION_NAME = "grid_option"
+ }
+}
diff --git a/src/com/android/customization/picker/grid/shared/model/GridOptionItemModel.kt b/src/com/android/customization/picker/grid/shared/model/GridOptionItemModel.kt
new file mode 100644
index 00000000..1fb01be0
--- /dev/null
+++ b/src/com/android/customization/picker/grid/shared/model/GridOptionItemModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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.grid.shared.model
+
+import kotlinx.coroutines.flow.StateFlow
+
+data class GridOptionItemModel(
+ val name: String,
+ val cols: Int,
+ val rows: Int,
+ val isSelected: StateFlow<Boolean>,
+ val onSelected: suspend () -> Unit,
+)
diff --git a/src/com/android/customization/picker/clock/utils/ClockDescriptionUtils.kt b/src/com/android/customization/picker/grid/shared/model/GridOptionItemsModel.kt
index 28ea4a3f..e5b33c52 100644
--- a/src/com/android/customization/picker/clock/utils/ClockDescriptionUtils.kt
+++ b/src/com/android/customization/picker/grid/shared/model/GridOptionItemsModel.kt
@@ -12,15 +12,16 @@
* 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.clock.utils
-/** Provides clock description for accessibility purposes. */
-interface ClockDescriptionUtils {
+package com.android.customization.picker.grid.shared.model
- /**
- * TODO (b/287507746) : Migrate the clock description to system UI or a shared library, instead
- * of preserving at the Wallpaper Picker side.
- */
- fun getDescription(clockId: String): String
+sealed class GridOptionItemsModel {
+ data class Loaded(
+ val options: List<GridOptionItemModel>,
+ ) : GridOptionItemsModel()
+ data class Error(
+ val throwable: Throwable?,
+ ) : GridOptionItemsModel()
}
diff --git a/src/com/android/customization/picker/grid/ui/binder/GridIconViewBinder.kt b/src/com/android/customization/picker/grid/ui/binder/GridIconViewBinder.kt
new file mode 100644
index 00000000..9fc88a0e
--- /dev/null
+++ b/src/com/android/customization/picker/grid/ui/binder/GridIconViewBinder.kt
@@ -0,0 +1,17 @@
+package com.android.customization.picker.grid.ui.binder
+
+import android.widget.ImageView
+import com.android.customization.picker.grid.ui.viewmodel.GridIconViewModel
+import com.android.customization.widget.GridTileDrawable
+
+object GridIconViewBinder {
+ fun bind(view: ImageView, viewModel: GridIconViewModel) {
+ view.setImageDrawable(
+ GridTileDrawable(
+ viewModel.columns,
+ viewModel.rows,
+ viewModel.path,
+ )
+ )
+ }
+}
diff --git a/src/com/android/customization/picker/grid/ui/binder/GridScreenBinder.kt b/src/com/android/customization/picker/grid/ui/binder/GridScreenBinder.kt
new file mode 100644
index 00000000..bcb37379
--- /dev/null
+++ b/src/com/android/customization/picker/grid/ui/binder/GridScreenBinder.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 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.grid.ui.binder
+
+import android.view.View
+import android.widget.Button
+import android.widget.ImageView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.picker.common.ui.view.ItemSpacing
+import com.android.customization.picker.grid.ui.viewmodel.GridIconViewModel
+import com.android.customization.picker.grid.ui.viewmodel.GridScreenViewModel
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter
+import com.android.wallpaper.picker.option.ui.binder.OptionItemBinder
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.launch
+
+object GridScreenBinder {
+ fun bind(
+ view: View,
+ viewModel: GridScreenViewModel,
+ lifecycleOwner: LifecycleOwner,
+ backgroundDispatcher: CoroutineDispatcher,
+ onOptionsChanged: () -> Unit,
+ isGridApplyButtonEnabled: Boolean,
+ onOptionApplied: () -> Unit,
+ ) {
+ val optionView: RecyclerView = view.requireViewById(R.id.options)
+ optionView.layoutManager =
+ LinearLayoutManager(
+ view.context,
+ RecyclerView.HORIZONTAL,
+ /* reverseLayout= */ false,
+ )
+ optionView.addItemDecoration(ItemSpacing(ItemSpacing.ITEM_SPACING_DP))
+ val adapter =
+ OptionItemAdapter(
+ layoutResourceId = R.layout.grid_option,
+ lifecycleOwner = lifecycleOwner,
+ backgroundDispatcher = backgroundDispatcher,
+ foregroundTintSpec =
+ OptionItemBinder.TintSpec(
+ selectedColor = view.context.getColor(R.color.system_on_surface),
+ unselectedColor = view.context.getColor(R.color.system_on_surface),
+ ),
+ bindIcon = { foregroundView: View, gridIcon: GridIconViewModel ->
+ val imageView = foregroundView as? ImageView
+ imageView?.let { GridIconViewBinder.bind(imageView, gridIcon) }
+ }
+ )
+ optionView.adapter = adapter
+
+ if (isGridApplyButtonEnabled) {
+ val applyButton: Button = view.requireViewById(R.id.apply_button)
+ applyButton.visibility = View.VISIBLE
+ view.requireViewById<View>(R.id.apply_button_note).visibility = View.VISIBLE
+ applyButton.setOnClickListener { onOptionApplied() }
+ }
+
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.optionItems.collect { options ->
+ adapter.setItems(options)
+ onOptionsChanged()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/grid/ui/fragment/GridFragment.kt b/src/com/android/customization/picker/grid/ui/fragment/GridFragment.kt
new file mode 100644
index 00000000..2a301b40
--- /dev/null
+++ b/src/com/android/customization/picker/grid/ui/fragment/GridFragment.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2023 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.grid.ui.fragment
+
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.Toast
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import androidx.lifecycle.ViewModelProvider
+import androidx.transition.Transition
+import androidx.transition.doOnStart
+import com.android.customization.model.CustomizationManager.Callback
+import com.android.customization.module.ThemePickerInjector
+import com.android.customization.picker.grid.domain.interactor.GridInteractor
+import com.android.customization.picker.grid.ui.binder.GridScreenBinder
+import com.android.customization.picker.grid.ui.viewmodel.GridScreenViewModel
+import com.android.wallpaper.R
+import com.android.wallpaper.config.BaseFlags
+import com.android.wallpaper.module.CurrentWallpaperInfoFactory
+import com.android.wallpaper.module.CustomizationSections
+import com.android.wallpaper.module.InjectorProvider
+import com.android.wallpaper.picker.AppbarFragment
+import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
+import com.android.wallpaper.picker.customization.ui.binder.ScreenPreviewBinder
+import com.android.wallpaper.picker.customization.ui.viewmodel.ScreenPreviewViewModel
+import com.android.wallpaper.util.PreviewUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private val TAG = GridFragment::class.java.simpleName
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class GridFragment : AppbarFragment() {
+
+ private lateinit var gridInteractor: GridInteractor
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val view =
+ inflater.inflate(
+ R.layout.fragment_grid,
+ container,
+ false,
+ )
+ setUpToolbar(view)
+
+ val isGridApplyButtonEnabled = BaseFlags.get().isGridApplyButtonEnabled(requireContext())
+
+ val injector = InjectorProvider.getInjector() as ThemePickerInjector
+
+ val wallpaperInfoFactory = injector.getCurrentWallpaperInfoFactory(requireContext())
+ var screenPreviewBinding =
+ bindScreenPreview(
+ view,
+ wallpaperInfoFactory,
+ injector.getWallpaperInteractor(requireContext()),
+ injector.getGridInteractor(requireContext())
+ )
+
+ val viewModelFactory = injector.getGridScreenViewModelFactory(requireContext())
+ gridInteractor = injector.getGridInteractor(requireContext())
+ GridScreenBinder.bind(
+ view = view,
+ viewModel =
+ ViewModelProvider(
+ this,
+ viewModelFactory,
+ )[GridScreenViewModel::class.java],
+ lifecycleOwner = this,
+ backgroundDispatcher = Dispatchers.IO,
+ onOptionsChanged = {
+ screenPreviewBinding.destroy()
+ screenPreviewBinding =
+ bindScreenPreview(
+ view,
+ wallpaperInfoFactory,
+ injector.getWallpaperInteractor(requireContext()),
+ gridInteractor,
+ )
+ if (isGridApplyButtonEnabled) {
+ val applyButton: Button = view.requireViewById(R.id.apply_button)
+ applyButton.isEnabled = !gridInteractor.isSelectedOptionApplied()
+ }
+ },
+ isGridApplyButtonEnabled = isGridApplyButtonEnabled,
+ onOptionApplied = {
+ gridInteractor.applySelectedOption(
+ object : Callback {
+ override fun onSuccess() {
+ Toast.makeText(
+ context,
+ getString(
+ R.string.toast_of_changing_grid,
+ gridInteractor.getSelectOptionNonSuspend()?.title
+ ),
+ Toast.LENGTH_SHORT
+ )
+ .show()
+ val applyButton: Button = view.requireViewById(R.id.apply_button)
+ applyButton.isEnabled = false
+ }
+
+ override fun onError(throwable: Throwable?) {
+ val errorMsg =
+ getString(
+ R.string.toast_of_failure_to_change_grid,
+ gridInteractor.getSelectOptionNonSuspend()?.title
+ )
+ Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show()
+ Log.e(TAG, errorMsg, throwable)
+ }
+ }
+ )
+ }
+ )
+
+ (returnTransition as? Transition)?.doOnStart {
+ view.requireViewById<View>(R.id.preview).isVisible = false
+ }
+
+ return view
+ }
+
+ override fun getDefaultTitle(): CharSequence {
+ return getString(R.string.grid_title)
+ }
+
+ override fun getToolbarColorId(): Int {
+ return android.R.color.transparent
+ }
+
+ override fun getToolbarTextColor(): Int {
+ return ContextCompat.getColor(requireContext(), R.color.system_on_surface)
+ }
+
+ private fun bindScreenPreview(
+ view: View,
+ wallpaperInfoFactory: CurrentWallpaperInfoFactory,
+ wallpaperInteractor: WallpaperInteractor,
+ gridInteractor: GridInteractor
+ ): ScreenPreviewBinder.Binding {
+ return ScreenPreviewBinder.bind(
+ activity = requireActivity(),
+ previewView = view.requireViewById(R.id.preview),
+ viewModel =
+ ScreenPreviewViewModel(
+ previewUtils =
+ PreviewUtils(
+ context = requireContext(),
+ authorityMetadataKey =
+ requireContext()
+ .getString(
+ R.string.grid_control_metadata_name,
+ ),
+ ),
+ initialExtrasProvider = {
+ val bundle = Bundle()
+ bundle.putString("name", gridInteractor.getSelectOptionNonSuspend()?.name)
+ bundle
+ },
+ wallpaperInfoProvider = {
+ suspendCancellableCoroutine { continuation ->
+ wallpaperInfoFactory.createCurrentWallpaperInfos(
+ context,
+ /* forceRefresh= */ true,
+ ) { homeWallpaper, lockWallpaper, _ ->
+ continuation.resume(homeWallpaper ?: lockWallpaper, null)
+ }
+ }
+ },
+ wallpaperInteractor = wallpaperInteractor,
+ screen = CustomizationSections.Screen.HOME_SCREEN,
+ ),
+ lifecycleOwner = viewLifecycleOwner,
+ offsetToStart = false,
+ onWallpaperPreviewDirty = { activity?.recreate() },
+ )
+ }
+
+ override fun onBackPressed(): Boolean {
+ if (BaseFlags.get().isGridApplyButtonEnabled(requireContext())) {
+ gridInteractor.clearSelectedOption()
+ }
+ return super.onBackPressed()
+ }
+}
diff --git a/src/com/android/customization/picker/grid/ui/section/GridSectionController.java b/src/com/android/customization/picker/grid/ui/section/GridSectionController.java
new file mode 100644
index 00000000..6ae9acd9
--- /dev/null
+++ b/src/com/android/customization/picker/grid/ui/section/GridSectionController.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 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.grid.ui.section;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.Observer;
+
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.grid.GridOption;
+import com.android.customization.model.grid.GridOptionsManager;
+import com.android.customization.picker.grid.ui.fragment.GridFragment;
+import com.android.customization.picker.grid.ui.view.GridSectionView;
+import com.android.wallpaper.R;
+import com.android.wallpaper.model.CustomizationSectionController;
+
+import java.util.List;
+
+/** A {@link CustomizationSectionController} for app grid. */
+public class GridSectionController implements CustomizationSectionController<GridSectionView> {
+
+ private static final String TAG = "GridSectionController";
+
+ private final GridOptionsManager mGridOptionsManager;
+ private final CustomizationSectionNavigationController mSectionNavigationController;
+ private final Observer<Object> mOptionChangeObserver;
+ private final LifecycleOwner mLifecycleOwner;
+ private TextView mSectionDescription;
+ private View mSectionTile;
+
+ public GridSectionController(
+ GridOptionsManager gridOptionsManager,
+ CustomizationSectionNavigationController sectionNavigationController,
+ LifecycleOwner lifecycleOwner,
+ boolean isRevampedUiEnabled) {
+ mGridOptionsManager = gridOptionsManager;
+ mSectionNavigationController = sectionNavigationController;
+ mLifecycleOwner = lifecycleOwner;
+ mOptionChangeObserver = o -> updateUi(/* reload= */ true);
+ }
+
+ @Override
+ public boolean isAvailable(Context context) {
+ return mGridOptionsManager.isAvailable();
+ }
+
+ @Override
+ public GridSectionView createView(Context context) {
+ final GridSectionView gridSectionView = (GridSectionView) LayoutInflater.from(context)
+ .inflate(R.layout.grid_section_view, /* root= */ null);
+ mSectionDescription = gridSectionView.findViewById(R.id.grid_section_description);
+ mSectionTile = gridSectionView.findViewById(R.id.grid_section_tile);
+
+ // Fetch grid options to show currently set grid.
+ updateUi(/* The result is getting when calling isAvailable(), so reload= */ false);
+ mGridOptionsManager.getOptionChangeObservable(/* handler= */ null).observe(
+ mLifecycleOwner,
+ mOptionChangeObserver);
+
+ gridSectionView.setOnClickListener(
+ v -> {
+ final Fragment gridFragment = new GridFragment();
+ mSectionNavigationController.navigateTo(gridFragment);
+ });
+
+ return gridSectionView;
+ }
+
+ @Override
+ public void release() {
+ if (mGridOptionsManager.isAvailable()) {
+ mGridOptionsManager.getOptionChangeObservable(/* handler= */ null).removeObserver(
+ mOptionChangeObserver
+ );
+ }
+ }
+
+ @Override
+ public void onTransitionOut() {
+ CustomizationSectionController.super.onTransitionOut();
+ }
+
+ private void updateUi(final boolean reload) {
+ mGridOptionsManager.fetchOptions(
+ new OptionsFetchedListener<GridOption>() {
+ @Override
+ public void onOptionsLoaded(List<GridOption> options) {
+ final String title = getActiveOption(options).getTitle();
+ mSectionDescription.setText(title);
+ }
+
+ @Override
+ public void onError(@Nullable Throwable throwable) {
+ if (throwable != null) {
+ Log.e(TAG, "Error loading grid options", throwable);
+ }
+ mSectionDescription.setText(R.string.something_went_wrong);
+ mSectionTile.setVisibility(View.GONE);
+ }
+ },
+ reload);
+ }
+
+ private GridOption getActiveOption(List<GridOption> options) {
+ return options.stream()
+ .filter(option -> option.isActive(mGridOptionsManager))
+ .findAny()
+ // For development only, as there should always be a grid set.
+ .orElse(options.get(0));
+ }
+}
diff --git a/src/com/android/customization/picker/grid/GridSectionView.java b/src/com/android/customization/picker/grid/ui/view/GridSectionView.java
index 58468e10..545ef197 100644
--- a/src/com/android/customization/picker/grid/GridSectionView.java
+++ b/src/com/android/customization/picker/grid/ui/view/GridSectionView.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.customization.picker.grid;
+package com.android.customization.picker.grid.ui.view;
import android.content.Context;
import android.util.AttributeSet;
diff --git a/src/com/android/customization/picker/clock/utils/ThemePickerClockDescriptionUtils.kt b/src/com/android/customization/picker/grid/ui/viewmodel/GridIconViewModel.kt
index a04ebfff..d12dc6c3 100644
--- a/src/com/android/customization/picker/clock/utils/ThemePickerClockDescriptionUtils.kt
+++ b/src/com/android/customization/picker/grid/ui/viewmodel/GridIconViewModel.kt
@@ -12,11 +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.clock.utils
-class ThemePickerClockDescriptionUtils : ClockDescriptionUtils {
- override fun getDescription(clockId: String): String {
- return ""
- }
-}
+package com.android.customization.picker.grid.ui.viewmodel
+
+data class GridIconViewModel(
+ val columns: Int,
+ val rows: Int,
+ val path: String,
+)
diff --git a/src/com/android/customization/picker/grid/ui/viewmodel/GridScreenViewModel.kt b/src/com/android/customization/picker/grid/ui/viewmodel/GridScreenViewModel.kt
new file mode 100644
index 00000000..179127d1
--- /dev/null
+++ b/src/com/android/customization/picker/grid/ui/viewmodel/GridScreenViewModel.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 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.grid.ui.viewmodel
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.Resources
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.customization.model.ResourceConstants
+import com.android.customization.picker.grid.domain.interactor.GridInteractor
+import com.android.customization.picker.grid.shared.model.GridOptionItemsModel
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+class GridScreenViewModel(
+ context: Context,
+ private val interactor: GridInteractor,
+) : ViewModel() {
+
+ @SuppressLint("StaticFieldLeak") // We're not leaking this context as it is the app context.
+ private val applicationContext = context.applicationContext
+
+ val optionItems: Flow<List<OptionItemViewModel<GridIconViewModel>>> =
+ interactor.options.map { model -> toViewModel(model) }
+
+ private fun toViewModel(
+ model: GridOptionItemsModel,
+ ): List<OptionItemViewModel<GridIconViewModel>> {
+ val iconShapePath =
+ applicationContext.resources.getString(
+ Resources.getSystem()
+ .getIdentifier(
+ ResourceConstants.CONFIG_ICON_MASK,
+ "string",
+ ResourceConstants.ANDROID_PACKAGE,
+ )
+ )
+
+ return when (model) {
+ is GridOptionItemsModel.Loaded ->
+ model.options.map { option ->
+ val text = Text.Loaded(option.name)
+ OptionItemViewModel<GridIconViewModel>(
+ key =
+ MutableStateFlow("${option.cols}x${option.rows}") as StateFlow<String>,
+ payload =
+ GridIconViewModel(
+ columns = option.cols,
+ rows = option.rows,
+ path = iconShapePath,
+ ),
+ text = text,
+ isSelected = option.isSelected,
+ onClicked =
+ option.isSelected.map { isSelected ->
+ if (!isSelected) {
+ { viewModelScope.launch { option.onSelected() } }
+ } else {
+ null
+ }
+ },
+ )
+ }
+ is GridOptionItemsModel.Error -> emptyList()
+ }
+ }
+
+ class Factory(
+ context: Context,
+ private val interactor: GridInteractor,
+ ) : ViewModelProvider.Factory {
+
+ private val applicationContext = context.applicationContext
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ return GridScreenViewModel(
+ context = applicationContext,
+ interactor = interactor,
+ )
+ as T
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt b/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt
index 954efa24..1a5254f8 100644
--- a/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt
+++ b/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt
@@ -21,6 +21,7 @@ import androidx.annotation.VisibleForTesting
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
+import com.android.customization.module.logging.ThemesUserEventLogger
import com.android.customization.picker.notifications.domain.interactor.NotificationsInteractor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -31,6 +32,7 @@ class NotificationSectionViewModel
@VisibleForTesting
constructor(
private val interactor: NotificationsInteractor,
+ private val logger: ThemesUserEventLogger,
) : ViewModel() {
/** Whether the switch should be on. */
@@ -39,16 +41,23 @@ constructor(
/** Notifies that the section has been clicked. */
fun onClicked() {
- viewModelScope.launch { interactor.toggleShowNotificationsOnLockScreenEnabled() }
+ viewModelScope.launch {
+ interactor.toggleShowNotificationsOnLockScreenEnabled()
+ logger.logLockScreenNotificationApplied(
+ interactor.getSettings().isShowNotificationsOnLockScreenEnabled
+ )
+ }
}
class Factory(
private val interactor: NotificationsInteractor,
+ private val logger: ThemesUserEventLogger,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return NotificationSectionViewModel(
interactor = interactor,
+ logger = logger,
)
as T
}
diff --git a/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt b/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt
index 71dfe1da..eb25af7a 100644
--- a/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt
+++ b/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt
@@ -42,10 +42,10 @@ import com.android.customization.picker.color.domain.interactor.ColorPickerInter
import com.android.wallpaper.R
import com.android.wallpaper.model.CustomizationSectionController
import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController
-import com.android.wallpaper.model.WallpaperColorsViewModel
import com.android.wallpaper.model.WallpaperPreviewNavigator
import com.android.wallpaper.module.CurrentWallpaperInfoFactory
import com.android.wallpaper.module.CustomizationSections
+import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository
import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewClickView
import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewSectionController
@@ -64,7 +64,7 @@ class PreviewWithClockCarouselSectionController(
private val lifecycleOwner: LifecycleOwner,
private val screen: CustomizationSections.Screen,
wallpaperInfoFactory: CurrentWallpaperInfoFactory,
- colorViewModel: WallpaperColorsViewModel,
+ wallpaperColorsRepository: WallpaperColorsRepository,
displayUtils: DisplayUtils,
clockCarouselViewModelFactory: ClockCarouselViewModel.Factory,
private val clockViewFactory: ClockViewFactory,
@@ -82,7 +82,7 @@ class PreviewWithClockCarouselSectionController(
lifecycleOwner,
screen,
wallpaperInfoFactory,
- colorViewModel,
+ wallpaperColorsRepository,
displayUtils,
wallpaperPreviewNavigator,
wallpaperInteractor,
diff --git a/src/com/android/customization/picker/preview/ui/section/PreviewWithThemeSectionController.kt b/src/com/android/customization/picker/preview/ui/section/PreviewWithThemeSectionController.kt
index c4d6be45..b3e778ba 100644
--- a/src/com/android/customization/picker/preview/ui/section/PreviewWithThemeSectionController.kt
+++ b/src/com/android/customization/picker/preview/ui/section/PreviewWithThemeSectionController.kt
@@ -25,16 +25,17 @@ import com.android.customization.model.themedicon.domain.interactor.ThemedIconIn
import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
import com.android.customization.picker.preview.ui.viewmodel.PreviewWithThemeViewModel
import com.android.wallpaper.R
-import com.android.wallpaper.model.WallpaperColorsViewModel
import com.android.wallpaper.model.WallpaperPreviewNavigator
import com.android.wallpaper.module.CurrentWallpaperInfoFactory
import com.android.wallpaper.module.CustomizationSections
+import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository
import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewSectionController
import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationPickerViewModel
import com.android.wallpaper.picker.customization.ui.viewmodel.ScreenPreviewViewModel
import com.android.wallpaper.util.DisplayUtils
import com.android.wallpaper.util.PreviewUtils
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
/**
@@ -46,7 +47,7 @@ open class PreviewWithThemeSectionController(
lifecycleOwner: LifecycleOwner,
private val screen: CustomizationSections.Screen,
private val wallpaperInfoFactory: CurrentWallpaperInfoFactory,
- private val colorViewModel: WallpaperColorsViewModel,
+ private val wallpaperColorsRepository: WallpaperColorsRepository,
displayUtils: DisplayUtils,
wallpaperPreviewNavigator: WallpaperPreviewNavigator,
private val wallpaperInteractor: WallpaperInteractor,
@@ -61,7 +62,7 @@ open class PreviewWithThemeSectionController(
lifecycleOwner,
screen,
wallpaperInfoFactory,
- colorViewModel,
+ wallpaperColorsRepository,
displayUtils,
wallpaperPreviewNavigator,
wallpaperInteractor,
@@ -69,6 +70,7 @@ open class PreviewWithThemeSectionController(
isTwoPaneAndSmallWidth,
customizationPickerViewModel,
) {
+ @OptIn(ExperimentalCoroutinesApi::class)
override fun createScreenPreviewViewModel(context: Context): ScreenPreviewViewModel {
return PreviewWithThemeViewModel(
previewUtils =
@@ -92,28 +94,28 @@ open class PreviewWithThemeSectionController(
wallpaperInfoProvider = { forceReload ->
suspendCancellableCoroutine { continuation ->
wallpaperInfoFactory.createCurrentWallpaperInfos(
- { homeWallpaper, lockWallpaper, _ ->
- val wallpaper =
- if (isOnLockScreen) {
- lockWallpaper ?: homeWallpaper
- } else {
- homeWallpaper ?: lockWallpaper
- }
- loadInitialColors(
- context = context,
- screen = screen,
- )
- continuation.resume(wallpaper, null)
- },
+ context,
forceReload,
- )
+ ) { homeWallpaper, lockWallpaper, _ ->
+ val wallpaper =
+ if (isOnLockScreen) {
+ lockWallpaper ?: homeWallpaper
+ } else {
+ homeWallpaper ?: lockWallpaper
+ }
+ loadInitialColors(
+ context = context,
+ screen = screen,
+ )
+ continuation.resume(wallpaper, null)
+ }
}
},
onWallpaperColorChanged = { colors ->
if (isOnLockScreen) {
- colorViewModel.setLockWallpaperColors(colors)
+ wallpaperColorsRepository.setLockWallpaperColors(colors)
} else {
- colorViewModel.setHomeWallpaperColors(colors)
+ wallpaperColorsRepository.setHomeWallpaperColors(colors)
}
},
initialExtrasProvider = { getInitialExtras(isOnLockScreen) },
diff --git a/src/com/android/customization/picker/quickaffordance/data/repository/KeyguardQuickAffordancePickerRepository.kt b/src/com/android/customization/picker/quickaffordance/data/repository/KeyguardQuickAffordancePickerRepository.kt
index b17af80d..6bfe3484 100644
--- a/src/com/android/customization/picker/quickaffordance/data/repository/KeyguardQuickAffordancePickerRepository.kt
+++ b/src/com/android/customization/picker/quickaffordance/data/repository/KeyguardQuickAffordancePickerRepository.kt
@@ -21,11 +21,11 @@ import com.android.customization.picker.quickaffordance.shared.model.KeyguardQui
import com.android.customization.picker.quickaffordance.shared.model.KeyguardQuickAffordancePickerSelectionModel as SelectionModel
import com.android.customization.picker.quickaffordance.shared.model.KeyguardQuickAffordancePickerSlotModel as SlotModel
import com.android.systemui.shared.customization.data.content.CustomizationProviderClient as Client
-import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
-import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.flow.shareIn
/**
* Abstracts access to application state related to functionality for selecting, picking, or setting
@@ -33,39 +33,25 @@ import kotlinx.coroutines.withContext
*/
class KeyguardQuickAffordancePickerRepository(
private val client: Client,
- private val backgroundDispatcher: CoroutineDispatcher,
+ private val scope: CoroutineScope
) {
- /** Whether the feature is enabled. */
- val isFeatureEnabled: Flow<Boolean> =
- client.observeFlags().map { flags -> flags.isFeatureEnabled() }
-
/** List of slots available on the device. */
val slots: Flow<List<SlotModel>> =
client.observeSlots().map { slots -> slots.map { slot -> slot.toModel() } }
/** List of all available quick affordances. */
val affordances: Flow<List<AffordanceModel>> =
- client.observeAffordances().map { affordances ->
- affordances.map { affordance -> affordance.toModel() }
- }
+ client
+ .observeAffordances()
+ .map { affordances -> affordances.map { affordance -> affordance.toModel() } }
+ .shareIn(scope, replay = 1, started = SharingStarted.Lazily)
/** List of slot-affordance pairs, modeling what the user has currently chosen for each slot. */
val selections: Flow<List<SelectionModel>> =
- client.observeSelections().map { selections ->
- selections.map { selection -> selection.toModel() }
- }
-
- suspend fun isFeatureEnabled(): Boolean {
- return withContext(backgroundDispatcher) { client.queryFlags().isFeatureEnabled() }
- }
-
- private fun List<Client.Flag>.isFeatureEnabled(): Boolean {
- return find { flag ->
- flag.name ==
- Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED
- }
- ?.value == true
- }
+ client
+ .observeSelections()
+ .map { selections -> selections.map { selection -> selection.toModel() } }
+ .shareIn(scope, replay = 1, started = SharingStarted.Lazily)
private fun Client.Slot.toModel(): SlotModel {
return SlotModel(
diff --git a/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordancePickerInteractor.kt b/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordancePickerInteractor.kt
index f154de65..3eca6241 100644
--- a/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordancePickerInteractor.kt
+++ b/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordancePickerInteractor.kt
@@ -64,7 +64,7 @@ class KeyguardQuickAffordancePickerInteractor(
}
/** Unselects all affordances from the slot with the given ID. */
- suspend fun unselectAll(slotId: String) {
+ suspend fun unselectAllFromSlot(slotId: String) {
client.deleteAllSelections(
slotId = slotId,
)
@@ -72,15 +72,15 @@ class KeyguardQuickAffordancePickerInteractor(
snapshotRestorer.get().storeSnapshot()
}
+ /** Unselects all affordances from all slots. */
+ suspend fun unselectAll() {
+ client.querySlots().forEach { client.deleteAllSelections(it.id) }
+ }
+
/** Returns a [Drawable] for the given resource ID, from the system UI package. */
suspend fun getAffordanceIcon(
@DrawableRes iconResourceId: Int,
): Drawable {
return client.getAffordanceIcon(iconResourceId)
}
-
- /** Returns `true` if the feature is enabled; `false` otherwise. */
- suspend fun isFeatureEnabled(): Boolean {
- return repository.isFeatureEnabled()
- }
}
diff --git a/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordanceSnapshotRestorer.kt b/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordanceSnapshotRestorer.kt
index 3c7928ce..fee0cb51 100644
--- a/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordanceSnapshotRestorer.kt
+++ b/src/com/android/customization/picker/quickaffordance/domain/interactor/KeyguardQuickAffordanceSnapshotRestorer.kt
@@ -42,9 +42,14 @@ class KeyguardQuickAffordanceSnapshotRestorer(
}
override suspend fun restoreToSnapshot(snapshot: RestorableSnapshot) {
+ // reset all current selections
+ interactor.unselectAll()
+
+ val allSelections = checkNotNull(snapshot.args[KEY_SELECTIONS])
+ if (allSelections.isEmpty()) return
+
val selections: List<Pair<String, String>> =
- checkNotNull(snapshot.args[KEY_SELECTIONS]).split(SELECTION_SEPARATOR).map { selection
- ->
+ allSelections.split(SELECTION_SEPARATOR).map { selection ->
val (slotId, affordanceId) = selection.split(SLOT_AFFORDANCE_SEPARATOR)
slotId to affordanceId
}
diff --git a/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt b/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt
index 8891b03f..0e3b7167 100644
--- a/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt
+++ b/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt
@@ -67,7 +67,9 @@ class SlotTabAdapter : RecyclerView.Adapter<SlotTabAdapter.ViewHolder>() {
.find { it.isSelected.value }
?.text
?.asString(holder.itemView.context)
- stateDescription?.let { holder.itemView.stateDescription = it }
+ holder.itemView.stateDescription =
+ stateDescription
+ ?: holder.itemView.resources.getString(R.string.keyguard_affordance_none)
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
diff --git a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt
index 091f484e..3ac52ad5 100644
--- a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt
+++ b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt
@@ -20,7 +20,11 @@ package com.android.customization.picker.quickaffordance.ui.binder
import android.app.Dialog
import android.content.Context
import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
import android.widget.ImageView
+import androidx.core.view.AccessibilityDelegateCompat
+import androidx.core.view.ViewCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
@@ -62,6 +66,26 @@ object KeyguardQuickAffordancePickerBinder {
slotTabView.layoutManager =
LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
slotTabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP))
+
+ // Setting a custom accessibility delegate so that the default content descriptions
+ // for items in a list aren't announced (for left & right shortcuts). We populate
+ // the content description for these shortcuts later on with the right (expected)
+ // values.
+ val slotTabViewDelegate: AccessibilityDelegateCompat =
+ object : AccessibilityDelegateCompat() {
+ override fun onRequestSendAccessibilityEvent(
+ host: ViewGroup,
+ child: View,
+ event: AccessibilityEvent
+ ): Boolean {
+ if (event.eventType != AccessibilityEvent.TYPE_VIEW_FOCUSED) {
+ child.contentDescription = null
+ }
+ return super.onRequestSendAccessibilityEvent(host, child, event)
+ }
+ }
+
+ ViewCompat.setAccessibilityDelegate(slotTabView, slotTabViewDelegate)
val affordancesAdapter =
OptionItemAdapter(
layoutResourceId = R.layout.keyguard_quick_affordance,
diff --git a/src/com/android/customization/picker/quickaffordance/ui/section/KeyguardQuickAffordanceSectionController.kt b/src/com/android/customization/picker/quickaffordance/ui/section/KeyguardQuickAffordanceSectionController.kt
index e0beeff0..0c7b250d 100644
--- a/src/com/android/customization/picker/quickaffordance/ui/section/KeyguardQuickAffordanceSectionController.kt
+++ b/src/com/android/customization/picker/quickaffordance/ui/section/KeyguardQuickAffordanceSectionController.kt
@@ -20,27 +20,23 @@ package com.android.customization.picker.quickaffordance.ui.section
import android.content.Context
import android.view.LayoutInflater
import androidx.lifecycle.LifecycleOwner
-import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
import com.android.customization.picker.quickaffordance.ui.binder.KeyguardQuickAffordanceSectionViewBinder
import com.android.customization.picker.quickaffordance.ui.fragment.KeyguardQuickAffordancePickerFragment
import com.android.customization.picker.quickaffordance.ui.view.KeyguardQuickAffordanceSectionView
import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
import com.android.wallpaper.R
+import com.android.wallpaper.config.BaseFlags
import com.android.wallpaper.model.CustomizationSectionController
import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController as NavigationController
-import kotlinx.coroutines.runBlocking
class KeyguardQuickAffordanceSectionController(
private val navigationController: NavigationController,
- private val interactor: KeyguardQuickAffordancePickerInteractor,
private val viewModel: KeyguardQuickAffordancePickerViewModel,
private val lifecycleOwner: LifecycleOwner,
) : CustomizationSectionController<KeyguardQuickAffordanceSectionView> {
- private val isFeatureEnabled: Boolean = runBlocking { interactor.isFeatureEnabled() }
-
override fun isAvailable(context: Context): Boolean {
- return isFeatureEnabled
+ return BaseFlags.get().isKeyguardQuickAffordanceEnabled(context)
}
override fun createView(context: Context): KeyguardQuickAffordanceSectionView {
diff --git a/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt b/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt
index f832cdeb..260c0d3b 100644
--- a/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt
+++ b/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt
@@ -26,6 +26,7 @@ import androidx.annotation.DrawableRes
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
+import com.android.customization.module.logging.ThemesUserEventLogger
import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
@@ -63,6 +64,7 @@ private constructor(
private val quickAffordanceInteractor: KeyguardQuickAffordancePickerInteractor,
private val wallpaperInteractor: WallpaperInteractor,
private val wallpaperInfoFactory: CurrentWallpaperInfoFactory,
+ private val logger: ThemesUserEventLogger,
) : ViewModel() {
@SuppressLint("StaticFieldLeak") private val applicationContext = context.applicationContext
@@ -92,11 +94,11 @@ private constructor(
wallpaperInfoProvider = { forceReload ->
suspendCancellableCoroutine { continuation ->
wallpaperInfoFactory.createCurrentWallpaperInfos(
- { homeWallpaper, lockWallpaper, _ ->
- continuation.resume(lockWallpaper ?: homeWallpaper, null)
- },
+ context,
forceReload,
- )
+ ) { homeWallpaper, lockWallpaper, _ ->
+ continuation.resume(lockWallpaper ?: homeWallpaper, null)
+ }
}
},
wallpaperInteractor = wallpaperInteractor,
@@ -158,7 +160,8 @@ private constructor(
Icon.Loaded(
drawable =
getAffordanceIcon(affordanceModel.iconResourceId),
- contentDescription = null,
+ contentDescription =
+ Text.Loaded(getSlotContentDescription(slot.id)),
),
text = Text.Loaded(affordanceModel.name),
isSelected = MutableStateFlow(true) as StateFlow<Boolean>,
@@ -214,7 +217,13 @@ private constructor(
if (!isSelected) {
{
viewModelScope.launch {
- quickAffordanceInteractor.unselectAll(selectedSlotId)
+ quickAffordanceInteractor.unselectAllFromSlot(
+ selectedSlotId
+ )
+ logger.logShortcutApplied(
+ shortcut = "none",
+ shortcutSlotId = selectedSlotId,
+ )
}
}
} else {
@@ -250,6 +259,10 @@ private constructor(
slotId = selectedSlotId,
affordanceId = affordance.id,
)
+ logger.logShortcutApplied(
+ shortcut = affordance.id,
+ shortcutSlotId = selectedSlotId,
+ )
}
}
} else {
@@ -423,6 +436,18 @@ private constructor(
)
}
+ private fun getSlotContentDescription(slotId: String): String {
+ return applicationContext.getString(
+ when (slotId) {
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START ->
+ R.string.keyguard_slot_name_bottom_start
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END ->
+ R.string.keyguard_slot_name_bottom_end
+ else -> error("No accessibility label for slot with ID \"$slotId\"!")
+ }
+ )
+ }
+
private suspend fun getAffordanceIcon(@DrawableRes iconResourceId: Int): Drawable {
return quickAffordanceInteractor.getAffordanceIcon(iconResourceId)
}
@@ -463,6 +488,7 @@ private constructor(
private val quickAffordanceInteractor: KeyguardQuickAffordancePickerInteractor,
private val wallpaperInteractor: WallpaperInteractor,
private val wallpaperInfoFactory: CurrentWallpaperInfoFactory,
+ private val logger: ThemesUserEventLogger,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
@@ -471,6 +497,7 @@ private constructor(
quickAffordanceInteractor = quickAffordanceInteractor,
wallpaperInteractor = wallpaperInteractor,
wallpaperInfoFactory = wallpaperInfoFactory,
+ logger = logger,
)
as T
}
diff --git a/src/com/android/customization/picker/theme/CustomThemeActivity.java b/src/com/android/customization/picker/theme/CustomThemeActivity.java
deleted file mode 100644
index 62a2f266..00000000
--- a/src/com/android/customization/picker/theme/CustomThemeActivity.java
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Copyright (C) 2019 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.theme;
-
-import android.app.AlertDialog.Builder;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
-
-import com.android.customization.model.CustomizationManager.Callback;
-import com.android.customization.model.theme.DefaultThemeProvider;
-import com.android.customization.model.theme.OverlayManagerCompat;
-import com.android.customization.model.theme.ThemeBundle;
-import com.android.customization.model.theme.ThemeBundleProvider;
-import com.android.customization.model.theme.ThemeManager;
-import com.android.customization.model.theme.custom.ColorOptionsProvider;
-import com.android.customization.model.theme.custom.CustomTheme;
-import com.android.customization.model.theme.custom.CustomThemeManager;
-import com.android.customization.model.theme.custom.FontOptionsProvider;
-import com.android.customization.model.theme.custom.IconOptionsProvider;
-import com.android.customization.model.theme.custom.ShapeOptionsProvider;
-import com.android.customization.model.theme.custom.ThemeComponentOption;
-import com.android.customization.model.theme.custom.ThemeComponentOption.ColorOption;
-import com.android.customization.model.theme.custom.ThemeComponentOption.FontOption;
-import com.android.customization.model.theme.custom.ThemeComponentOption.IconOption;
-import com.android.customization.model.theme.custom.ThemeComponentOption.ShapeOption;
-import com.android.customization.model.theme.custom.ThemeComponentOptionProvider;
-import com.android.customization.module.CustomizationInjector;
-import com.android.customization.module.ThemesUserEventLogger;
-import com.android.customization.picker.theme.CustomThemeStepFragment.CustomThemeComponentStepHost;
-import com.android.wallpaper.R;
-import com.android.wallpaper.module.InjectorProvider;
-import com.android.wallpaper.picker.AppbarFragment.AppbarFragmentHost;
-
-import org.json.JSONException;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class CustomThemeActivity extends FragmentActivity implements
- AppbarFragmentHost, CustomThemeComponentStepHost {
- public static final String EXTRA_THEME_ID = "CustomThemeActivity.ThemeId";
- public static final String EXTRA_THEME_TITLE = "CustomThemeActivity.ThemeTitle";
- public static final String EXTRA_THEME_PACKAGES = "CustomThemeActivity.ThemePackages";
- public static final int REQUEST_CODE_CUSTOM_THEME = 1;
- public static final int RESULT_THEME_DELETED = 10;
- public static final int RESULT_THEME_APPLIED = 20;
-
- private static final String TAG = "CustomThemeActivity";
- private static final String KEY_STATE_CURRENT_STEP = "CustomThemeActivity.currentStep";
-
- private ThemesUserEventLogger mUserEventLogger;
- private List<ComponentStep<?>> mSteps;
- private int mCurrentStep;
- private CustomThemeManager mCustomThemeManager;
- private ThemeManager mThemeManager;
- private TextView mNextButton;
- private TextView mPreviousButton;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
- mUserEventLogger = (ThemesUserEventLogger) injector.getUserEventLogger(this);
- ThemeBundleProvider themeProvider =
- new DefaultThemeProvider(this, injector.getCustomizationPreferences(this));
- Intent intent = getIntent();
- CustomTheme customTheme = null;
- if (intent != null && intent.hasExtra(EXTRA_THEME_PACKAGES)
- && intent.hasExtra(EXTRA_THEME_TITLE) && intent.hasExtra(EXTRA_THEME_ID)) {
- try {
- CustomTheme.Builder themeBuilder = themeProvider.parseCustomTheme(
- intent.getStringExtra(EXTRA_THEME_PACKAGES));
- if (themeBuilder != null) {
- themeBuilder.setId(intent.getStringExtra(EXTRA_THEME_ID));
- themeBuilder.setTitle(intent.getStringExtra(EXTRA_THEME_TITLE));
- customTheme = themeBuilder.build(this);
- }
- } catch (JSONException e) {
- Log.w(TAG, "Couldn't parse provided custom theme, will override it");
- }
- }
-
- mThemeManager = injector.getThemeManager(
- new DefaultThemeProvider(this, injector.getCustomizationPreferences(this)),
- this,
- new OverlayManagerCompat(this),
- mUserEventLogger);
- mThemeManager.fetchOptions(null, false);
- mCustomThemeManager = CustomThemeManager.create(customTheme, mThemeManager);
- if (savedInstanceState != null) {
- mCustomThemeManager.readCustomTheme(themeProvider, savedInstanceState);
- }
-
- int currentStep = 0;
- if (savedInstanceState != null) {
- currentStep = savedInstanceState.getInt(KEY_STATE_CURRENT_STEP);
- }
- initSteps(currentStep);
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_custom_theme);
- mNextButton = findViewById(R.id.next_button);
- mNextButton.setOnClickListener(view -> onNextOrApply());
- mPreviousButton = findViewById(R.id.previous_button);
- mPreviousButton.setOnClickListener(view -> onBackPressed());
-
- FragmentManager fm = getSupportFragmentManager();
- Fragment fragment = fm.findFragmentById(R.id.fragment_container);
- if (fragment == null) {
- // Navigate to the first step
- navigateToStep(0);
- }
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(KEY_STATE_CURRENT_STEP, mCurrentStep);
- if (mCustomThemeManager != null) {
- mCustomThemeManager.saveCustomTheme(this, outState);
- }
- }
-
- private void navigateToStep(int i) {
- FragmentManager fragmentManager = getSupportFragmentManager();
- ComponentStep step = mSteps.get(i);
- Fragment fragment = step.getFragment(mCustomThemeManager.getOriginalTheme().getTitle());
-
- FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
- fragmentTransaction.replace(R.id.fragment_container, fragment);
- // Don't add step 0 to the back stack so that going back from it just finishes the Activity
- if (i > 0) {
- fragmentTransaction.addToBackStack("Step " + i);
- }
- fragmentTransaction.commit();
- fragmentManager.executePendingTransactions();
- updateNavigationButtonLabels();
- }
-
- private void initSteps(int currentStep) {
- mSteps = new ArrayList<>();
- OverlayManagerCompat manager = new OverlayManagerCompat(this);
- mSteps.add(new FontStep(new FontOptionsProvider(this, manager), 0));
- mSteps.add(new IconStep(new IconOptionsProvider(this, manager), 1));
- mSteps.add(new ColorStep(new ColorOptionsProvider(this, manager, mCustomThemeManager), 2));
- mSteps.add(new ShapeStep(new ShapeOptionsProvider(this, manager), 3));
- mSteps.add(new NameStep(4));
- mCurrentStep = currentStep;
- }
-
- private void onNextOrApply() {
- CustomThemeStepFragment stepFragment = getCurrentStepFragment();
- if (stepFragment instanceof CustomThemeComponentFragment) {
- CustomThemeComponentFragment fragment = (CustomThemeComponentFragment) stepFragment;
- mCustomThemeManager.apply(fragment.getSelectedOption(), new Callback() {
- @Override
- public void onSuccess() {
- navigateToStep(mCurrentStep + 1);
- }
-
- @Override
- public void onError(@Nullable Throwable throwable) {
- Log.w(TAG, "Error applying custom theme component", throwable);
- Toast.makeText(CustomThemeActivity.this, R.string.apply_theme_error_msg,
- Toast.LENGTH_LONG).show();
- }
- });
- } else if (stepFragment instanceof CustomThemeNameFragment) {
- CustomThemeNameFragment fragment = (CustomThemeNameFragment) stepFragment;
- CustomTheme originalTheme = mCustomThemeManager.getOriginalTheme();
-
- // We're on the last step, apply theme and leave
- CustomTheme themeToApply = mCustomThemeManager.buildPartialCustomTheme(this,
- originalTheme.getId(), fragment.getThemeName());
-
- // If the current theme is equal to the original theme being edited, then
- // don't search for an equivalent, let the user apply the same one by keeping
- // it null.
- ThemeBundle equivalent = (originalTheme.isEquivalent(themeToApply))
- ? null : mThemeManager.findThemeByPackages(themeToApply);
-
- if (equivalent != null) {
- Builder builder =
- new Builder(CustomThemeActivity.this);
- builder.setTitle(getString(R.string.use_style_instead_title,
- equivalent.getTitle()))
- .setMessage(getString(R.string.use_style_instead_body,
- equivalent.getTitle()))
- .setPositiveButton(getString(R.string.use_style_button,
- equivalent.getTitle()),
- (dialogInterface, i) -> applyTheme(equivalent))
- .setNegativeButton(R.string.no_thanks, null)
- .create()
- .show();
- } else {
- applyTheme(themeToApply);
- }
- } else {
- throw new IllegalStateException("Unknown CustomThemeStepFragment");
- }
- }
-
- private void applyTheme(ThemeBundle themeToApply) {
- mThemeManager.apply(themeToApply, new Callback() {
- @Override
- public void onSuccess() {
- overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
- Toast.makeText(getApplicationContext(), R.string.applied_theme_msg,
- Toast.LENGTH_LONG).show();
- setResult(RESULT_THEME_APPLIED);
- finish();
- }
-
- @Override
- public void onError(@Nullable Throwable throwable) {
- Log.w(TAG, "Error applying custom theme", throwable);
- Toast.makeText(CustomThemeActivity.this,
- R.string.apply_theme_error_msg,
- Toast.LENGTH_LONG).show();
- }
- });
- }
-
- private CustomThemeStepFragment getCurrentStepFragment() {
- return (CustomThemeStepFragment)
- getSupportFragmentManager().findFragmentById(R.id.fragment_container);
- }
-
- @Override
- public void setCurrentStep(int i) {
- mCurrentStep = i;
- updateNavigationButtonLabels();
- }
-
- private void updateNavigationButtonLabels() {
- mPreviousButton.setVisibility(mCurrentStep == 0 ? View.INVISIBLE : View.VISIBLE);
- mNextButton.setText((mCurrentStep < mSteps.size() -1) ? R.string.custom_theme_next
- : R.string.apply_btn);
- }
-
- @Override
- public void delete() {
- mThemeManager.removeCustomTheme(mCustomThemeManager.getOriginalTheme());
- setResult(RESULT_THEME_DELETED);
- finish();
- }
-
- @Override
- public void cancel() {
- finish();
- }
-
- @Override
- public ThemeComponentOptionProvider<? extends ThemeComponentOption> getComponentOptionProvider(
- int position) {
- return mSteps.get(position).provider;
- }
-
- @Override
- public CustomThemeManager getCustomThemeManager() {
- return mCustomThemeManager;
- }
-
- @Override
- public void onUpArrowPressed() {
- // Skip it because CustomThemeStepFragment will implement cancel button
- // (instead of up arrow) on action bar.
- }
-
- @Override
- public boolean isUpArrowSupported() {
- // Skip it because CustomThemeStepFragment will implement cancel button
- // (instead of up arrow) on action bar.
- return false;
- }
-
- /**
- * Represents a step in selecting a custom theme, picking a particular component (eg font,
- * color, shape, etc).
- * Each step has a Fragment instance associated that instances of this class will provide.
- */
- private static abstract class ComponentStep<T extends ThemeComponentOption> {
- @StringRes final int titleResId;
- @StringRes final int accessibilityResId;
- final ThemeComponentOptionProvider<T> provider;
- final int position;
- private CustomThemeStepFragment mFragment;
-
- protected ComponentStep(@StringRes int titleResId, @StringRes int accessibilityResId,
- ThemeComponentOptionProvider<T> provider, int position) {
- this.titleResId = titleResId;
- this.accessibilityResId = accessibilityResId;
- this.provider = provider;
- this.position = position;
- }
-
- CustomThemeStepFragment getFragment(String title) {
- if (mFragment == null) {
- mFragment = createFragment(title);
- }
- return mFragment;
- }
-
- /**
- * @return a newly created fragment that will handle this step's UI.
- */
- abstract CustomThemeStepFragment createFragment(String title);
- }
-
- private class FontStep extends ComponentStep<FontOption> {
-
- protected FontStep(ThemeComponentOptionProvider<FontOption> provider,
- int position) {
- super(R.string.font_component_title, R.string.accessibility_custom_font_title, provider,
- position);
- }
-
- @Override
- CustomThemeComponentFragment createFragment(String title) {
- return CustomThemeComponentFragment.newInstance(
- title,
- position,
- titleResId,
- accessibilityResId);
- }
- }
-
- private class IconStep extends ComponentStep<IconOption> {
-
- protected IconStep(ThemeComponentOptionProvider<IconOption> provider,
- int position) {
- super(R.string.icon_component_title, R.string.accessibility_custom_icon_title, provider,
- position);
- }
-
- @Override
- CustomThemeComponentFragment createFragment(String title) {
- return CustomThemeComponentFragment.newInstance(
- title,
- position,
- titleResId,
- accessibilityResId);
- }
- }
-
- private class ColorStep extends ComponentStep<ColorOption> {
-
- protected ColorStep(ThemeComponentOptionProvider<ColorOption> provider,
- int position) {
- super(R.string.color_component_title, R.string.accessibility_custom_color_title,
- provider, position);
- }
-
- @Override
- CustomThemeComponentFragment createFragment(String title) {
- return CustomThemeComponentFragment.newInstance(
- title,
- position,
- titleResId,
- accessibilityResId);
- }
- }
-
- private class ShapeStep extends ComponentStep<ShapeOption> {
-
- protected ShapeStep(ThemeComponentOptionProvider<ShapeOption> provider,
- int position) {
- super(R.string.shape_component_title, R.string.accessibility_custom_shape_title,
- provider, position);
- }
-
- @Override
- CustomThemeComponentFragment createFragment(String title) {
- return CustomThemeComponentFragment.newInstance(
- title,
- position,
- titleResId,
- accessibilityResId);
- }
- }
-
- private class NameStep extends ComponentStep {
-
- protected NameStep(int position) {
- super(R.string.name_component_title, R.string.accessibility_custom_name_title, null,
- position);
- }
-
- @Override
- CustomThemeNameFragment createFragment(String title) {
- return CustomThemeNameFragment.newInstance(
- title,
- position,
- titleResId,
- accessibilityResId);
- }
- }
-}
diff --git a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
deleted file mode 100644
index a1e99677..00000000
--- a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2019 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.theme;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.customization.model.theme.custom.ThemeComponentOption;
-import com.android.customization.model.theme.custom.ThemeComponentOptionProvider;
-import com.android.customization.widget.OptionSelectorController;
-import com.android.customization.widget.OptionSelectorController.CheckmarkStyle;
-import com.android.wallpaper.R;
-import com.android.wallpaper.picker.AppbarFragment;
-
-public class CustomThemeComponentFragment extends CustomThemeStepFragment {
- private static final String ARG_USE_GRID_LAYOUT = "CustomThemeComponentFragment.use_grid";;
-
- public static CustomThemeComponentFragment newInstance(CharSequence toolbarTitle, int position,
- int titleResId, int accessibilityResId) {
- return newInstance(toolbarTitle, position, titleResId, accessibilityResId, false);
- }
-
- public static CustomThemeComponentFragment newInstance(CharSequence toolbarTitle, int position,
- int titleResId, int accessibilityResId, boolean allowGridLayout) {
- CustomThemeComponentFragment fragment = new CustomThemeComponentFragment();
- Bundle arguments = AppbarFragment.createArguments(toolbarTitle);
- arguments.putInt(ARG_KEY_POSITION, position);
- arguments.putInt(ARG_KEY_TITLE_RES_ID, titleResId);
- arguments.putInt(ARG_KEY_ACCESSIBILITY_RES_ID, accessibilityResId);
- arguments.putBoolean(ARG_USE_GRID_LAYOUT, allowGridLayout);
- fragment.setArguments(arguments);
- return fragment;
- }
-
- private ThemeComponentOptionProvider<? extends ThemeComponentOption> mProvider;
- private boolean mUseGridLayout;
-
- private RecyclerView mOptionsContainer;
- private OptionSelectorController<ThemeComponentOption> mOptionsController;
- private ThemeComponentOption mSelectedOption;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mUseGridLayout = getArguments().getBoolean(ARG_USE_GRID_LAYOUT);
- mProvider = mHost.getComponentOptionProvider(mPosition);
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View view = super.onCreateView(inflater, container, savedInstanceState);
- mOptionsContainer = view.findViewById(R.id.options_container);
- mPreviewContainer = view.findViewById(R.id.component_preview_content);
- mTitle = view.findViewById(R.id.component_options_title);
- mTitle.setText(mTitleResId);
- setUpOptions();
-
- return view;
- }
-
- @Override
- protected int getFragmentLayoutResId() {
- return R.layout.fragment_custom_theme_component;
- }
-
- public ThemeComponentOption getSelectedOption() {
- return mSelectedOption;
- }
-
- private void bindPreview() {
- mSelectedOption.bindPreview(mPreviewContainer);
- }
-
- private void setUpOptions() {
- mProvider.fetch(options -> {
- mOptionsController = new OptionSelectorController(
- mOptionsContainer, options, mUseGridLayout, CheckmarkStyle.NONE);
-
- mOptionsController.addListener(selected -> {
- mSelectedOption = (ThemeComponentOption) selected;
- bindPreview();
- // Preview and apply. The selection will be kept whatever user goes to previous page
- // or encounter system config changes, the current selection can be recovered.
- mCustomThemeManager.apply(mSelectedOption, /* callback= */ null);
- });
- mOptionsController.initOptions(mCustomThemeManager);
-
- for (ThemeComponentOption option : options) {
- if (option.isActive(mCustomThemeManager)) {
- mSelectedOption = option;
- break;
- }
- }
- if (mSelectedOption == null) {
- mSelectedOption = options.get(0);
- }
- mOptionsController.setSelectedOption(mSelectedOption);
- }, false);
- }
-}
diff --git a/src/com/android/customization/picker/theme/CustomThemeNameFragment.java b/src/com/android/customization/picker/theme/CustomThemeNameFragment.java
deleted file mode 100644
index ea9099fc..00000000
--- a/src/com/android/customization/picker/theme/CustomThemeNameFragment.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2019 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.theme;
-
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.ImageView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.customization.model.theme.ThemeBundle.PreviewInfo;
-import com.android.customization.model.theme.custom.CustomTheme;
-import com.android.customization.module.CustomizationInjector;
-import com.android.customization.module.CustomizationPreferences;
-import com.android.customization.picker.WallpaperPreviewer;
-import com.android.wallpaper.R;
-import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
-import com.android.wallpaper.module.InjectorProvider;
-import com.android.wallpaper.picker.AppbarFragment;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-
-/** Fragment of naming a custom theme. */
-public class CustomThemeNameFragment extends CustomThemeStepFragment {
-
- private static final String TAG = "CustomThemeNameFragment";
-
- public static CustomThemeNameFragment newInstance(CharSequence toolbarTitle, int position,
- int titleResId, int accessibilityResId) {
- CustomThemeNameFragment fragment = new CustomThemeNameFragment();
- Bundle arguments = AppbarFragment.createArguments(toolbarTitle);
- arguments.putInt(ARG_KEY_POSITION, position);
- arguments.putInt(ARG_KEY_TITLE_RES_ID, titleResId);
- arguments.putInt(ARG_KEY_ACCESSIBILITY_RES_ID, accessibilityResId);
- fragment.setArguments(arguments);
- return fragment;
- }
-
- private EditText mNameEditor;
- private ImageView mWallpaperImage;
- private ThemeOptionPreviewer mThemeOptionPreviewer;
- private CustomizationPreferences mCustomizationPreferences;
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View view = super.onCreateView(inflater, container, savedInstanceState);
- mTitle = view.findViewById(R.id.component_options_title);
- mTitle.setText(mTitleResId);
- CurrentWallpaperInfoFactory currentWallpaperFactory = InjectorProvider.getInjector()
- .getCurrentWallpaperInfoFactory(getActivity().getApplicationContext());
- CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
- mCustomizationPreferences = injector.getCustomizationPreferences(getContext());
-
- // Set theme option.
- ViewGroup previewContainer = view.findViewById(R.id.theme_preview_container);
- mThemeOptionPreviewer = new ThemeOptionPreviewer(getLifecycle(), getContext(),
- previewContainer);
- PreviewInfo previewInfo = mCustomThemeManager.buildCustomThemePreviewInfo(getContext());
- mThemeOptionPreviewer.setPreviewInfo(previewInfo);
-
- // Set wallpaper background.
- mWallpaperImage = view.findViewById(R.id.wallpaper_preview_image);
- final WallpaperPreviewer wallpaperPreviewer = new WallpaperPreviewer(
- getLifecycle(),
- getActivity(),
- mWallpaperImage,
- view.findViewById(R.id.wallpaper_preview_surface));
- currentWallpaperFactory.createCurrentWallpaperInfos(
- (homeWallpaper, lockWallpaper, presentationMode) -> {
- wallpaperPreviewer.setWallpaper(homeWallpaper,
- mThemeOptionPreviewer::updateColorForLauncherWidgets);
- }, false);
-
- // Set theme default name.
- mNameEditor = view.findViewById(R.id.custom_theme_name);
- mNameEditor.setText(getOriginalThemeName());
- return view;
- }
-
- private String getOriginalThemeName() {
- CustomTheme originalTheme = mCustomThemeManager.getOriginalTheme();
- if (originalTheme == null || !originalTheme.isDefined()) {
- // For new custom theme. use custom themes amount plus 1 as default naming.
- String serializedThemes = mCustomizationPreferences.getSerializedCustomThemes();
- int customThemesCount = 0;
- if (!TextUtils.isEmpty(serializedThemes)) {
- try {
- JSONArray customThemes = new JSONArray(serializedThemes);
- customThemesCount = customThemes.length();
- } catch (JSONException e) {
- Log.w(TAG, "Couldn't read stored custom theme");
- }
- }
- return getContext().getString(
- R.string.custom_theme_title, customThemesCount + 1);
- } else {
- // For existing custom theme, keep its name as default naming.
- return originalTheme.getTitle();
- }
- }
-
- @Override
- protected int getFragmentLayoutResId() {
- return R.layout.fragment_custom_theme_name;
- }
-
- public String getThemeName() {
- return mNameEditor.getText().toString();
- }
-}
diff --git a/src/com/android/customization/picker/theme/CustomThemeStepFragment.java b/src/com/android/customization/picker/theme/CustomThemeStepFragment.java
deleted file mode 100644
index 3f07431d..00000000
--- a/src/com/android/customization/picker/theme/CustomThemeStepFragment.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package com.android.customization.picker.theme;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-
-import com.android.customization.model.theme.custom.CustomThemeManager;
-import com.android.customization.model.theme.custom.ThemeComponentOption;
-import com.android.customization.model.theme.custom.ThemeComponentOptionProvider;
-import com.android.wallpaper.R;
-import com.android.wallpaper.picker.AppbarFragment;
-
-abstract class CustomThemeStepFragment extends AppbarFragment {
- protected static final String ARG_KEY_POSITION = "CustomThemeStepFragment.position";
- protected static final String ARG_KEY_TITLE_RES_ID = "CustomThemeStepFragment.title_res";
- protected static final String ARG_KEY_ACCESSIBILITY_RES_ID =
- "CustomThemeStepFragment.accessibility_res";
- protected CustomThemeComponentStepHost mHost;
- protected CustomThemeManager mCustomThemeManager;
- protected int mPosition;
- protected ViewGroup mPreviewContainer;
- protected TextView mTitle;
- @StringRes
- protected int mTitleResId;
- @StringRes
- protected int mAccessibilityResId;
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mHost = (CustomThemeComponentStepHost) context;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- mHost.setCurrentStep(mPosition);
- }
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mPosition = getArguments().getInt(ARG_KEY_POSITION);
- mTitleResId = getArguments().getInt(ARG_KEY_TITLE_RES_ID);
- mAccessibilityResId = getArguments().getInt(ARG_KEY_ACCESSIBILITY_RES_ID);
- mCustomThemeManager = mHost.getCustomThemeManager();
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(
- getFragmentLayoutResId(), container, /* attachToRoot */ false);
- // No original theme means it's a new one, so no toolbar icon for deleting it is needed
- if (mCustomThemeManager.getOriginalTheme() == null
- || !mCustomThemeManager.getOriginalTheme().isDefined()) {
- setUpToolbar(view);
- } else {
- setUpToolbar(view, R.menu.custom_theme_editor_menu);
- mToolbar.getMenu().getItem(0).setIconTintList(
- getContext().getColorStateList(R.color.toolbar_icon_tint));
- }
- Drawable closeIcon = getResources().getDrawable(R.drawable.ic_close_24px, null).mutate();
- closeIcon.setTintList(getResources().getColorStateList(R.color.toolbar_icon_tint, null));
- mToolbar.setNavigationIcon(closeIcon);
-
- mToolbar.setNavigationContentDescription(R.string.cancel);
- mToolbar.setNavigationOnClickListener(v -> mHost.cancel());
-
- mPreviewContainer = view.findViewById(R.id.component_preview_content);
- return view;
- }
-
- @Override
- protected String getAccessibilityTitle() {
- return getString(mAccessibilityResId);
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- if (item.getItemId() == R.id.custom_theme_delete) {
- AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
- builder.setMessage(R.string.delete_custom_theme_confirmation)
- .setPositiveButton(R.string.delete_custom_theme_button,
- (dialogInterface, i) -> mHost.delete())
- .setNegativeButton(R.string.cancel, null)
- .create()
- .show();
- return true;
- }
- return super.onMenuItemClick(item);
- }
-
- protected abstract int getFragmentLayoutResId();
-
- public interface CustomThemeComponentStepHost {
- void delete();
- void cancel();
- ThemeComponentOptionProvider<? extends ThemeComponentOption> getComponentOptionProvider(
- int position);
-
- CustomThemeManager getCustomThemeManager();
-
- void setCurrentStep(int step);
- }
-}
diff --git a/src/com/android/customization/picker/theme/ThemeFragment.java b/src/com/android/customization/picker/theme/ThemeFragment.java
deleted file mode 100644
index 3a9a56f5..00000000
--- a/src/com/android/customization/picker/theme/ThemeFragment.java
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Copyright (C) 2018 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.theme;
-
-import static android.app.Activity.RESULT_OK;
-
-import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY;
-import static com.android.wallpaper.widget.BottomActionBar.BottomAction.CUSTOMIZE;
-import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.widget.ContentLoadingProgressBar;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.customization.model.CustomizationManager.Callback;
-import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
-import com.android.customization.model.CustomizationOption;
-import com.android.customization.model.theme.ThemeBundle;
-import com.android.customization.model.theme.ThemeManager;
-import com.android.customization.model.theme.custom.CustomTheme;
-import com.android.customization.module.ThemesUserEventLogger;
-import com.android.customization.picker.WallpaperPreviewer;
-import com.android.customization.widget.OptionSelectorController;
-import com.android.wallpaper.R;
-import com.android.wallpaper.model.WallpaperInfo;
-import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
-import com.android.wallpaper.module.InjectorProvider;
-import com.android.wallpaper.picker.AppbarFragment;
-import com.android.wallpaper.widget.BottomActionBar;
-import com.android.wallpaper.widget.BottomActionBar.AccessibilityCallback;
-import com.android.wallpaper.widget.BottomActionBar.BottomSheetContent;
-
-import java.util.List;
-
-/**
- * Fragment that contains the main UI for selecting and applying a ThemeBundle.
- */
-public class ThemeFragment extends AppbarFragment {
-
- private static final String TAG = "ThemeFragment";
- private static final String KEY_SELECTED_THEME = "ThemeFragment.SelectedThemeBundle";
- private static final String KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE =
- "ThemeFragment.bottomActionBarVisible";
- private static final int FULL_PREVIEW_REQUEST_CODE = 1000;
-
- /**
- * Interface to be implemented by an Activity hosting a {@link ThemeFragment}
- */
- public interface ThemeFragmentHost {
- ThemeManager getThemeManager();
- }
- public static ThemeFragment newInstance(CharSequence title) {
- ThemeFragment fragment = new ThemeFragment();
- fragment.setArguments(AppbarFragment.createArguments(title));
- return fragment;
- }
-
- private RecyclerView mOptionsContainer;
- private OptionSelectorController<ThemeBundle> mOptionsController;
- private ThemeManager mThemeManager;
- private ThemesUserEventLogger mEventLogger;
- private ThemeBundle mSelectedTheme;
- private ContentLoadingProgressBar mLoading;
- private View mContent;
- private View mError;
- private WallpaperInfo mCurrentHomeWallpaper;
- private CurrentWallpaperInfoFactory mCurrentWallpaperFactory;
- private BottomActionBar mBottomActionBar;
- private WallpaperPreviewer mWallpaperPreviewer;
- private ImageView mWallpaperImage;
- private ThemeOptionPreviewer mThemeOptionPreviewer;
- private ThemeInfoView mThemeInfoView;
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mThemeManager = ((ThemeFragmentHost) context).getThemeManager();
- mEventLogger = (ThemesUserEventLogger)
- InjectorProvider.getInjector().getUserEventLogger(context);
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(
- R.layout.fragment_theme_picker, container, /* attachToRoot */ false);
- setUpToolbar(view);
-
- mContent = view.findViewById(R.id.content_section);
- mLoading = view.findViewById(R.id.loading_indicator);
- mError = view.findViewById(R.id.error_section);
- mCurrentWallpaperFactory = InjectorProvider.getInjector()
- .getCurrentWallpaperInfoFactory(getActivity().getApplicationContext());
- mOptionsContainer = view.findViewById(R.id.options_container);
-
- mThemeOptionPreviewer = new ThemeOptionPreviewer(
- getLifecycle(),
- getContext(),
- view.findViewById(R.id.theme_preview_container));
-
- // Set Wallpaper background.
- mWallpaperImage = view.findViewById(R.id.wallpaper_preview_image);
- mWallpaperPreviewer = new WallpaperPreviewer(
- getLifecycle(),
- getActivity(),
- mWallpaperImage,
- view.findViewById(R.id.wallpaper_preview_surface));
- mCurrentWallpaperFactory.createCurrentWallpaperInfos(
- (homeWallpaper, lockWallpaper, presentationMode) -> {
- mCurrentHomeWallpaper = homeWallpaper;
- mWallpaperPreviewer.setWallpaper(mCurrentHomeWallpaper,
- mThemeOptionPreviewer::updateColorForLauncherWidgets);
- }, false);
- return view;
- }
-
- @Override
- protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
- super.onBottomActionBarReady(bottomActionBar);
- mBottomActionBar = bottomActionBar;
- mBottomActionBar.showActionsOnly(INFORMATION, APPLY);
- mBottomActionBar.setActionClickListener(APPLY, v -> {
- mBottomActionBar.disableActions();
- applyTheme();
- });
-
- mBottomActionBar.bindBottomSheetContentWithAction(
- new ThemeInfoContent(getContext()), INFORMATION);
- mBottomActionBar.setActionClickListener(CUSTOMIZE, this::onCustomizeClicked);
-
- // Update target view's accessibility param since it will be blocked by the bottom sheet
- // when expanded.
- mBottomActionBar.setAccessibilityCallback(new AccessibilityCallback() {
- @Override
- public void onBottomSheetCollapsed() {
- mOptionsContainer.setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
-
- @Override
- public void onBottomSheetExpanded() {
- mOptionsContainer.setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
- }
- });
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- // Setup options here when all views are ready(including BottomActionBar), since we need to
- // update views after options are loaded.
- setUpOptions(savedInstanceState);
- }
-
- private void applyTheme() {
- mThemeManager.apply(mSelectedTheme, new Callback() {
- @Override
- public void onSuccess() {
- Toast.makeText(getContext(), R.string.applied_theme_msg, Toast.LENGTH_LONG).show();
- getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
- getActivity().finish();
- }
-
- @Override
- public void onError(@Nullable Throwable throwable) {
- Log.w(TAG, "Error applying theme", throwable);
- // Since we disabled it when clicked apply button.
- mBottomActionBar.enableActions();
- mBottomActionBar.hide();
- Toast.makeText(getContext(), R.string.apply_theme_error_msg,
- Toast.LENGTH_LONG).show();
- }
- });
- }
-
- @Override
- public void onSaveInstanceState(@NonNull Bundle outState) {
- super.onSaveInstanceState(outState);
- if (mSelectedTheme != null && !mSelectedTheme.isActive(mThemeManager)) {
- outState.putString(KEY_SELECTED_THEME, mSelectedTheme.getSerializedPackages());
- }
- if (mBottomActionBar != null) {
- outState.putBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE, mBottomActionBar.isVisible());
- }
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME) {
- if (resultCode == CustomThemeActivity.RESULT_THEME_DELETED) {
- mSelectedTheme = null;
- reloadOptions();
- } else if (resultCode == CustomThemeActivity.RESULT_THEME_APPLIED) {
- getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
- getActivity().finish();
- } else {
- if (mSelectedTheme != null) {
- mOptionsController.setSelectedOption(mSelectedTheme);
- // Set selected option above will show BottomActionBar,
- // hide BottomActionBar for the mis-trigger.
- mBottomActionBar.hide();
- } else {
- reloadOptions();
- }
- }
- } else if (requestCode == FULL_PREVIEW_REQUEST_CODE && resultCode == RESULT_OK) {
- applyTheme();
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
-
- private void onCustomizeClicked(View view) {
- if (mSelectedTheme instanceof CustomTheme) {
- navigateToCustomTheme((CustomTheme) mSelectedTheme);
- }
- }
-
- private void hideError() {
- mContent.setVisibility(View.VISIBLE);
- mError.setVisibility(View.GONE);
- }
-
- private void showError() {
- mLoading.hide();
- mContent.setVisibility(View.GONE);
- mError.setVisibility(View.VISIBLE);
- }
-
- private void setUpOptions(@Nullable Bundle savedInstanceState) {
- hideError();
- mLoading.show();
- mThemeManager.fetchOptions(new OptionsFetchedListener<ThemeBundle>() {
- @Override
- public void onOptionsLoaded(List<ThemeBundle> options) {
- mOptionsController = new OptionSelectorController<>(mOptionsContainer, options);
- mOptionsController.initOptions(mThemeManager);
-
- // Find out the selected theme option.
- // 1. Find previously selected theme.
- String previouslySelected = savedInstanceState != null
- ? savedInstanceState.getString(KEY_SELECTED_THEME) : null;
- ThemeBundle previouslySelectedTheme = null;
- ThemeBundle activeTheme = null;
- for (ThemeBundle theme : options) {
- if (previouslySelected != null
- && previouslySelected.equals(theme.getSerializedPackages())) {
- previouslySelectedTheme = theme;
- }
- if (theme.isActive(mThemeManager)) {
- activeTheme = theme;
- }
- }
- // 2. Use active theme if no previously selected theme.
- mSelectedTheme = previouslySelectedTheme != null
- ? previouslySelectedTheme
- : activeTheme;
- // 3. Select the first system theme(default theme currently)
- // if there is no matching custom enabled theme.
- if (mSelectedTheme == null) {
- mSelectedTheme = findFirstSystemThemeBundle(options);
- }
-
- mOptionsController.setSelectedOption(mSelectedTheme);
- onOptionSelected(mSelectedTheme);
- restoreBottomActionBarVisibility(savedInstanceState);
-
- mOptionsController.addListener(selectedOption -> {
- onOptionSelected(selectedOption);
- if (!isAddCustomThemeOption(selectedOption)) {
- mBottomActionBar.show();
- }
- });
- mLoading.hide();
- }
- @Override
- public void onError(@Nullable Throwable throwable) {
- if (throwable != null) {
- Log.e(TAG, "Error loading theme bundles", throwable);
- }
- showError();
- }
- }, false);
- }
-
- private void reloadOptions() {
- mThemeManager.fetchOptions(options -> {
- mOptionsController.resetOptions(options);
- for (ThemeBundle theme : options) {
- if (theme.isActive(mThemeManager)) {
- mSelectedTheme = theme;
- break;
- }
- }
- if (mSelectedTheme == null) {
- mSelectedTheme = findFirstSystemThemeBundle(options);
- }
- mOptionsController.setSelectedOption(mSelectedTheme);
- // Set selected option above will show BottomActionBar,
- // hide BottomActionBar for the mis-trigger.
- mBottomActionBar.hide();
- }, true);
- }
-
- private ThemeBundle findFirstSystemThemeBundle(List<ThemeBundle> options) {
- for (ThemeBundle bundle : options) {
- if (!(bundle instanceof CustomTheme)) {
- return bundle;
- }
- }
- return null;
- }
-
- private void onOptionSelected(CustomizationOption selectedOption) {
- if (isAddCustomThemeOption(selectedOption)) {
- navigateToCustomTheme((CustomTheme) selectedOption);
- } else {
- mSelectedTheme = (ThemeBundle) selectedOption;
- mSelectedTheme.setOverrideThemeWallpaper(mCurrentHomeWallpaper);
- mEventLogger.logThemeSelected(mSelectedTheme,
- selectedOption instanceof CustomTheme);
- mThemeOptionPreviewer.setPreviewInfo(mSelectedTheme.getPreviewInfo());
- if (mThemeInfoView != null && mSelectedTheme != null) {
- mThemeInfoView.populateThemeInfo(mSelectedTheme);
- }
-
- if (selectedOption instanceof CustomTheme) {
- mBottomActionBar.showActionsOnly(INFORMATION, CUSTOMIZE, APPLY);
- } else {
- mBottomActionBar.showActionsOnly(INFORMATION, APPLY);
- }
- }
- }
-
- private void restoreBottomActionBarVisibility(@Nullable Bundle savedInstanceState) {
- boolean isBottomActionBarVisible = savedInstanceState != null
- && savedInstanceState.getBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE);
- if (isBottomActionBarVisible) {
- mBottomActionBar.show();
- } else {
- mBottomActionBar.hide();
- }
- }
-
- private boolean isAddCustomThemeOption(CustomizationOption option) {
- return option instanceof CustomTheme && !((CustomTheme) option).isDefined();
- }
-
- private void navigateToCustomTheme(CustomTheme themeToEdit) {
- Intent intent = new Intent(getActivity(), CustomThemeActivity.class);
- intent.putExtra(CustomThemeActivity.EXTRA_THEME_TITLE, themeToEdit.getTitle());
- intent.putExtra(CustomThemeActivity.EXTRA_THEME_ID, themeToEdit.getId());
- intent.putExtra(CustomThemeActivity.EXTRA_THEME_PACKAGES,
- themeToEdit.getSerializedPackages());
- startActivityForResult(intent, CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME);
- }
-
- private final class ThemeInfoContent extends BottomSheetContent<ThemeInfoView> {
-
- private ThemeInfoContent(Context context) {
- super(context);
- }
-
- @Override
- public int getViewId() {
- return R.layout.theme_info_view;
- }
-
- @Override
- public void onViewCreated(ThemeInfoView view) {
- mThemeInfoView = view;
- if (mSelectedTheme != null) {
- mThemeInfoView.populateThemeInfo(mSelectedTheme);
- }
- }
- }
-}
diff --git a/src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java b/src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java
deleted file mode 100644
index 3ba64ecc..00000000
--- a/src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2020 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.theme;
-
-import static android.app.Activity.RESULT_OK;
-
-import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY;
-import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.customization.model.theme.DefaultThemeProvider;
-import com.android.customization.model.theme.ThemeBundle;
-import com.android.customization.model.theme.ThemeBundleProvider;
-import com.android.customization.module.CustomizationInjector;
-import com.android.customization.picker.WallpaperPreviewer;
-import com.android.wallpaper.R;
-import com.android.wallpaper.model.WallpaperInfo;
-import com.android.wallpaper.module.InjectorProvider;
-import com.android.wallpaper.picker.AppbarFragment;
-import com.android.wallpaper.widget.BottomActionBar;
-import com.android.wallpaper.widget.BottomActionBar.BottomSheetContent;
-
-import com.bumptech.glide.Glide;
-
-import org.json.JSONException;
-
-/** A Fragment for theme full preview page. */
-public class ThemeFullPreviewFragment extends AppbarFragment {
- private static final String TAG = "ThemeFullPreviewFragment";
-
- public static final String EXTRA_THEME_OPTION_TITLE = "theme_option_title";
- protected static final String EXTRA_THEME_OPTION = "theme_option";
- protected static final String EXTRA_WALLPAPER_INFO = "wallpaper_info";
- protected static final String EXTRA_CAN_APPLY_FROM_FULL_PREVIEW = "can_apply";
-
- private WallpaperInfo mWallpaper;
- private ThemeBundle mThemeBundle;
- private boolean mCanApplyFromFullPreview;
-
- /**
- * Returns a new {@link ThemeFullPreviewFragment} with the provided title and bundle arguments
- * set.
- */
- public static ThemeFullPreviewFragment newInstance(CharSequence title, Bundle intentBundle) {
- ThemeFullPreviewFragment fragment = new ThemeFullPreviewFragment();
- Bundle bundle = new Bundle();
- bundle.putAll(AppbarFragment.createArguments(title));
- bundle.putAll(intentBundle);
- fragment.setArguments(bundle);
- return fragment;
- }
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mWallpaper = getArguments().getParcelable(EXTRA_WALLPAPER_INFO);
- mCanApplyFromFullPreview = getArguments().getBoolean(EXTRA_CAN_APPLY_FROM_FULL_PREVIEW);
- CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
- ThemeBundleProvider themeProvider = new DefaultThemeProvider(
- getContext(), injector.getCustomizationPreferences(getContext()));
- try {
- ThemeBundle.Builder builder = themeProvider.parseThemeBundle(
- getArguments().getString(EXTRA_THEME_OPTION));
- if (builder != null) {
- builder.setTitle(getArguments().getString(EXTRA_THEME_OPTION_TITLE));
- mThemeBundle = builder.build(getContext());
- }
- } catch (JSONException e) {
- Log.w(TAG, "Couldn't parse provided custom theme, will override it");
- // TODO(chihhangchuang): Handle the error case.
- }
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(
- R.layout.fragment_theme_full_preview, container, /* attachToRoot */ false);
- setUpToolbar(view);
- Glide.get(getContext()).clearMemory();
-
- // Set theme option.
- final ThemeOptionPreviewer themeOptionPreviewer = new ThemeOptionPreviewer(
- getLifecycle(),
- getContext(),
- view.findViewById(R.id.theme_preview_container));
- themeOptionPreviewer.setPreviewInfo(mThemeBundle.getPreviewInfo());
-
- // Set wallpaper background.
- ImageView wallpaperImageView = view.findViewById(R.id.wallpaper_preview_image);
- final WallpaperPreviewer wallpaperPreviewer = new WallpaperPreviewer(
- getLifecycle(),
- getActivity(),
- wallpaperImageView,
- view.findViewById(R.id.wallpaper_preview_surface));
- wallpaperPreviewer.setWallpaper(mWallpaper,
- themeOptionPreviewer::updateColorForLauncherWidgets);
- return view;
- }
-
- @Override
- protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
- super.onBottomActionBarReady(bottomActionBar);
- if (mCanApplyFromFullPreview) {
- bottomActionBar.showActionsOnly(INFORMATION, APPLY);
- bottomActionBar.setActionClickListener(APPLY, v -> finishActivityWithResultOk());
- } else {
- bottomActionBar.showActionsOnly(INFORMATION);
- }
- bottomActionBar.bindBottomSheetContentWithAction(
- new ThemeInfoContent(getContext()), INFORMATION);
- bottomActionBar.show();
- }
-
- private void finishActivityWithResultOk() {
- Activity activity = requireActivity();
- activity.overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
- Intent intent = new Intent();
- activity.setResult(RESULT_OK, intent);
- activity.finish();
- }
-
- private final class ThemeInfoContent extends BottomSheetContent<ThemeInfoView> {
-
- private ThemeInfoContent(Context context) {
- super(context);
- }
-
- @Override
- public int getViewId() {
- return R.layout.theme_info_view;
- }
-
- @Override
- public void onViewCreated(ThemeInfoView view) {
- view.populateThemeInfo(mThemeBundle);
- }
- }
-}
diff --git a/src/com/android/customization/picker/theme/ThemeInfoView.java b/src/com/android/customization/picker/theme/ThemeInfoView.java
deleted file mode 100644
index e929c4d2..00000000
--- a/src/com/android/customization/picker/theme/ThemeInfoView.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2020 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.theme;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.customization.model.theme.ThemeBundle;
-import com.android.wallpaper.R;
-
-/** A view for displaying style info. */
-public class ThemeInfoView extends LinearLayout {
- private static final int WIFI_ICON_PREVIEW_INDEX = 0;
- private static final int SHAPE_PREVIEW_INDEX = 0;
-
- private TextView mTitle;
- private TextView mFontPreviewTextView;
- private ImageView mIconPreviewImageView;
- private ImageView mAppPreviewImageView;
- private ImageView mShapePreviewImageView;
-
- public ThemeInfoView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mTitle = findViewById(R.id.style_info_title);
- mFontPreviewTextView = findViewById(R.id.font_preview);
- mIconPreviewImageView = findViewById(R.id.qs_preview_icon);
- mAppPreviewImageView = findViewById(R.id.app_preview_icon);
- mShapePreviewImageView = findViewById(R.id.shape_preview_icon);
- }
-
- /** Populates theme info. */
- public void populateThemeInfo(@NonNull ThemeBundle selectedTheme) {
- ThemeBundle.PreviewInfo previewInfo = selectedTheme.getPreviewInfo();
-
- if (previewInfo != null) {
- mTitle.setText(getContext().getString(R.string.style_info_description));
- if (previewInfo.headlineFontFamily != null) {
- mTitle.setTypeface(previewInfo.headlineFontFamily);
- mFontPreviewTextView.setTypeface(previewInfo.headlineFontFamily);
- }
-
- if (previewInfo.icons.get(WIFI_ICON_PREVIEW_INDEX) != null) {
- mIconPreviewImageView.setImageDrawable(
- previewInfo.icons.get(WIFI_ICON_PREVIEW_INDEX));
- }
-
- if (previewInfo.shapeAppIcons.get(SHAPE_PREVIEW_INDEX) != null) {
- mAppPreviewImageView.setBackground(
- previewInfo.shapeAppIcons.get(SHAPE_PREVIEW_INDEX).getDrawableCopy());
- }
-
- if (previewInfo.shapeDrawable != null) {
- mShapePreviewImageView.setImageDrawable(previewInfo.shapeDrawable);
- mShapePreviewImageView.setImageTintList(
- ColorStateList.valueOf(previewInfo.resolveAccentColor(getResources())));
- }
- }
- }
-}
diff --git a/src/com/android/customization/picker/theme/ThemeOptionPreviewer.java b/src/com/android/customization/picker/theme/ThemeOptionPreviewer.java
deleted file mode 100644
index 14b53ec4..00000000
--- a/src/com/android/customization/picker/theme/ThemeOptionPreviewer.java
+++ /dev/null
@@ -1,405 +0,0 @@
-/*
- * Copyright (C) 2020 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.theme;
-
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
-import android.app.WallpaperColors;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.text.format.DateFormat;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-import androidx.cardview.widget.CardView;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.OnLifecycleEvent;
-
-import com.android.customization.model.theme.ThemeBundle;
-import com.android.customization.model.theme.ThemeBundle.PreviewInfo;
-import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon;
-import com.android.wallpaper.R;
-import com.android.wallpaper.util.ResourceUtils;
-import com.android.wallpaper.util.ScreenSizeCalculator;
-import com.android.wallpaper.util.TimeUtils;
-import com.android.wallpaper.util.TimeUtils.TimeTicker;
-
-import java.util.Calendar;
-import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
-
-/** A class to load the {@link ThemeBundle} preview to the view. */
-class ThemeOptionPreviewer implements LifecycleObserver {
- private static final String DATE_FORMAT = "EEEE, MMM d";
-
- // Maps which icon from ResourceConstants#ICONS_FOR_PREVIEW.
- private static final int ICON_WIFI = 0;
- private static final int ICON_BLUETOOTH = 1;
- private static final int ICON_FLASHLIGHT = 3;
- private static final int ICON_AUTO_ROTATE = 4;
- private static final int ICON_CELLULAR_SIGNAL = 6;
- private static final int ICON_BATTERY = 7;
-
- // Icons in the top bar (fake "status bar") with the particular order.
- private static final int [] sTopBarIconToPreviewIcon = new int [] {
- ICON_WIFI, ICON_CELLULAR_SIGNAL, ICON_BATTERY };
-
- // Ids of app icon shape preview.
- private int[] mShapeAppIconIds = {
- R.id.shape_preview_icon_0, R.id.shape_preview_icon_1,
- R.id.shape_preview_icon_2, R.id.shape_preview_icon_3
- };
- private int[] mShapeIconAppNameIds = {
- R.id.shape_preview_icon_app_name_0, R.id.shape_preview_icon_app_name_1,
- R.id.shape_preview_icon_app_name_2, R.id.shape_preview_icon_app_name_3
- };
-
- // Ids of color/icons section.
- private int[][] mColorTileIconIds = {
- new int[] { R.id.preview_color_qs_0_icon, ICON_WIFI},
- new int[] { R.id.preview_color_qs_1_icon, ICON_BLUETOOTH},
- new int[] { R.id.preview_color_qs_2_icon, ICON_FLASHLIGHT},
- new int[] { R.id.preview_color_qs_3_icon, ICON_AUTO_ROTATE},
- };
- private int[] mColorTileIds = {
- R.id.preview_color_qs_0_bg, R.id.preview_color_qs_1_bg,
- R.id.preview_color_qs_2_bg, R.id.preview_color_qs_3_bg
- };
- private int[] mColorButtonIds = {
- R.id.preview_check_selected, R.id.preview_radio_selected, R.id.preview_toggle_selected
- };
-
- private final Context mContext;
-
- private View mContentView;
- private TextView mStatusBarClock;
- private TextView mSmartSpaceDate;
- private TimeTicker mTicker;
-
- private boolean mHasPreviewInfoSet;
- private boolean mHasWallpaperColorSet;
-
- ThemeOptionPreviewer(Lifecycle lifecycle, Context context, ViewGroup previewContainer) {
- lifecycle.addObserver(this);
-
- mContext = context;
- mContentView = LayoutInflater.from(context).inflate(
- R.layout.theme_preview_content, /* root= */ null);
- mContentView.setVisibility(View.INVISIBLE);
- mStatusBarClock = mContentView.findViewById(R.id.theme_preview_clock);
- mSmartSpaceDate = mContentView.findViewById(R.id.smart_space_date);
- updateTime();
- final float screenAspectRatio =
- ScreenSizeCalculator.getInstance().getScreenAspectRatio(mContext);
- Configuration config = mContext.getResources().getConfiguration();
- final boolean directionLTR = config.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
- previewContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View view, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- // Calculate the full preview card height and width.
- final int fullPreviewCardHeight = getFullPreviewCardHeight();
- final int fullPreviewCardWidth = (int) (fullPreviewCardHeight / screenAspectRatio);
-
- // Relayout the content view to match full preview card size.
- mContentView.measure(
- makeMeasureSpec(fullPreviewCardWidth, EXACTLY),
- makeMeasureSpec(fullPreviewCardHeight, EXACTLY));
- mContentView.layout(0, 0, fullPreviewCardWidth, fullPreviewCardHeight);
-
- // Scale the content view from full preview size to the container size. For full
- // preview, the scale value is 1.
- float scale = (float) previewContainer.getMeasuredHeight() / fullPreviewCardHeight;
- mContentView.setScaleX(scale);
- mContentView.setScaleY(scale);
- // The pivot point is centered by default, set to (0, 0).
- mContentView.setPivotX(directionLTR ? 0f : mContentView.getMeasuredWidth());
- mContentView.setPivotY(0f);
-
- // Ensure there will be only one content view in the container.
- previewContainer.removeAllViews();
- // Finally, add the content view to the container.
- previewContainer.addView(
- mContentView,
- mContentView.getMeasuredWidth(),
- mContentView.getMeasuredHeight());
-
- previewContainer.removeOnLayoutChangeListener(this);
- }
- });
- }
-
- /** Loads the Theme option preview into the container view. */
- public void setPreviewInfo(PreviewInfo previewInfo) {
- setHeadlineFont(previewInfo.headlineFontFamily);
- setBodyFont(previewInfo.bodyFontFamily);
- setTopBarIcons(previewInfo.icons);
- setAppIconShape(previewInfo.shapeAppIcons);
- setColorAndIconsSection(previewInfo.icons, previewInfo.shapeDrawable,
- previewInfo.resolveAccentColor(mContext.getResources()));
- setColorAndIconsBoxRadius(previewInfo.bottomSheeetCornerRadius);
- setQsbRadius(previewInfo.bottomSheeetCornerRadius);
- mHasPreviewInfoSet = true;
- showPreviewIfHasAllConfigSet();
- }
-
- /**
- * Updates the color of widgets in launcher (like top status bar, smart space, and app name
- * text) which will change its content color according to different wallpapers.
- *
- * @param colors the {@link WallpaperColors} of the wallpaper, or {@code null} to use light
- * color as default
- */
- public void updateColorForLauncherWidgets(@Nullable WallpaperColors colors) {
- boolean useLightTextColor = colors == null
- || (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0;
- int textColor = mContext.getColor(useLightTextColor
- ? android.R.color.white
- : android.R.color.black);
- int textShadowColor = mContext.getColor(useLightTextColor
- ? android.R.color.tertiary_text_dark
- : android.R.color.transparent);
- // Update the top status bar clock text color.
- mStatusBarClock.setTextColor(textColor);
- // Update the top status bar icon color.
- ViewGroup iconsContainer = mContentView.findViewById(R.id.theme_preview_top_bar_icons);
- for (int i = 0; i < iconsContainer.getChildCount(); i++) {
- ((ImageView) iconsContainer.getChildAt(i))
- .setImageTintList(ColorStateList.valueOf(textColor));
- }
- // Update smart space date color.
- mSmartSpaceDate.setTextColor(textColor);
- mSmartSpaceDate.setShadowLayer(
- mContext.getResources().getDimension(
- R.dimen.smartspace_preview_key_ambient_shadow_blur),
- /* dx = */ 0,
- /* dy = */ 0,
- textShadowColor);
-
- // Update shape app icon name text color.
- for (int id : mShapeIconAppNameIds) {
- TextView appName = mContentView.findViewById(id);
- appName.setTextColor(textColor);
- appName.setShadowLayer(
- mContext.getResources().getDimension(
- R.dimen.preview_theme_app_name_key_ambient_shadow_blur),
- /* dx = */ 0,
- /* dy = */ 0,
- textShadowColor);
- }
-
- mHasWallpaperColorSet = true;
- showPreviewIfHasAllConfigSet();
- }
-
- @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
- @MainThread
- public void onResume() {
- mTicker = TimeTicker.registerNewReceiver(mContext, this::updateTime);
- updateTime();
- }
-
- @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
- @MainThread
- public void onPause() {
- if (mContext != null) {
- mContext.unregisterReceiver(mTicker);
- }
- }
-
- private void showPreviewIfHasAllConfigSet() {
- if (mHasPreviewInfoSet && mHasWallpaperColorSet
- && mContentView.getVisibility() != View.VISIBLE) {
- mContentView.setAlpha(0f);
- mContentView.setVisibility(View.VISIBLE);
- mContentView.animate().alpha(1f)
- .setStartDelay(50)
- .setDuration(200)
- .setInterpolator(AnimationUtils.loadInterpolator(mContext,
- android.R.interpolator.fast_out_linear_in))
- .start();
- }
- }
-
- private void setHeadlineFont(Typeface headlineFont) {
- mStatusBarClock.setTypeface(headlineFont);
- mSmartSpaceDate.setTypeface(headlineFont);
-
- // Update font of color/icons section title.
- TextView colorIconsSectionTitle = mContentView.findViewById(R.id.color_icons_section_title);
- colorIconsSectionTitle.setTypeface(headlineFont);
- }
-
- private void setBodyFont(Typeface bodyFont) {
- // Update font of app names.
- for (int id : mShapeIconAppNameIds) {
- TextView appName = mContentView.findViewById(id);
- appName.setTypeface(bodyFont);
- }
- }
-
- private void setTopBarIcons(List<Drawable> icons) {
- ViewGroup iconsContainer = mContentView.findViewById(R.id.theme_preview_top_bar_icons);
- for (int i = 0; i < iconsContainer.getChildCount(); i++) {
- int iconIndex = sTopBarIconToPreviewIcon[i];
- if (iconIndex < icons.size()) {
- ((ImageView) iconsContainer.getChildAt(i))
- .setImageDrawable(icons.get(iconIndex).getConstantState()
- .newDrawable().mutate());
- } else {
- iconsContainer.getChildAt(i).setVisibility(View.GONE);
- }
- }
- }
-
- private void setAppIconShape(List<ShapeAppIcon> appIcons) {
- for (int i = 0; i < mShapeAppIconIds.length && i < mShapeIconAppNameIds.length
- && i < appIcons.size(); i++) {
- ShapeAppIcon icon = appIcons.get(i);
- // Set app icon.
- ImageView iconView = mContentView.findViewById(mShapeAppIconIds[i]);
- iconView.setBackground(icon.getDrawableCopy());
- // Set app name.
- TextView appName = mContentView.findViewById(mShapeIconAppNameIds[i]);
- appName.setText(icon.getAppName());
- }
- }
-
- private void setColorAndIconsSection(List<Drawable> icons, Drawable shapeDrawable,
- int accentColor) {
- // Set QS icons and background.
- for (int i = 0; i < mColorTileIconIds.length && i < icons.size(); i++) {
- Drawable icon = icons.get(mColorTileIconIds[i][1]).getConstantState()
- .newDrawable().mutate();
- Drawable bgShape = shapeDrawable.getConstantState().newDrawable();
- bgShape.setTint(accentColor);
-
- ImageView bg = mContentView.findViewById(mColorTileIds[i]);
- bg.setImageDrawable(bgShape);
- ImageView fg = mContentView.findViewById(mColorTileIconIds[i][0]);
- fg.setImageDrawable(icon);
- }
-
- // Set color for Buttons (CheckBox, RadioButton, and Switch).
- ColorStateList tintList = getColorStateList(accentColor);
- for (int mColorButtonId : mColorButtonIds) {
- CompoundButton button = mContentView.findViewById(mColorButtonId);
- button.setButtonTintList(tintList);
- if (button instanceof Switch) {
- ((Switch) button).setThumbTintList(tintList);
- ((Switch) button).setTrackTintList(tintList);
- }
- }
- }
-
- private void setColorAndIconsBoxRadius(int cornerRadius) {
- ((CardView) mContentView.findViewById(R.id.color_icons_section)).setRadius(cornerRadius);
- }
-
- private void setQsbRadius(int cornerRadius) {
- View qsb = mContentView.findViewById(R.id.theme_qsb);
- if (qsb != null && qsb.getVisibility() == View.VISIBLE) {
- if (qsb.getBackground() instanceof GradientDrawable) {
- GradientDrawable bg = (GradientDrawable) qsb.getBackground();
- float radius = useRoundedQSB(cornerRadius)
- ? (float) qsb.getLayoutParams().height / 2 : cornerRadius;
- bg.setCornerRadii(new float[]{
- radius, radius, radius, radius,
- radius, radius, radius, radius});
- }
- }
- }
-
- private void updateTime() {
- Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
- if (mStatusBarClock != null) {
- mStatusBarClock.setText(TimeUtils.getFormattedTime(mContext, calendar));
- }
- if (mSmartSpaceDate != null) {
- String datePattern =
- DateFormat.getBestDateTimePattern(Locale.getDefault(), DATE_FORMAT);
- mSmartSpaceDate.setText(DateFormat.format(datePattern, calendar));
- }
- }
-
- private boolean useRoundedQSB(int cornerRadius) {
- return cornerRadius >= mContext.getResources().getDimensionPixelSize(
- R.dimen.roundCornerThreshold);
- }
-
- private ColorStateList getColorStateList(int accentColor) {
- int controlGreyColor =
- ResourceUtils.getColorAttr(mContext, android.R.attr.textColorTertiary);
- return new ColorStateList(
- new int[][]{
- new int[]{android.R.attr.state_selected},
- new int[]{android.R.attr.state_checked},
- new int[]{-android.R.attr.state_enabled},
- },
- new int[] {
- accentColor,
- accentColor,
- controlGreyColor
- }
- );
- }
-
- /**
- * Gets the screen height which does not include the system status bar and bottom navigation
- * bar.
- */
- private int getDisplayHeight() {
- final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
- return dm.heightPixels;
- }
-
- // The height of top tool bar (R.layout.section_header).
- private int getTopToolBarHeight() {
- final TypedValue typedValue = new TypedValue();
- return mContext.getTheme().resolveAttribute(
- android.R.attr.actionBarSize, typedValue, true)
- ? TypedValue.complexToDimensionPixelSize(
- typedValue.data, mContext.getResources().getDisplayMetrics())
- : 0;
- }
-
- private int getFullPreviewCardHeight() {
- final Resources res = mContext.getResources();
- return getDisplayHeight()
- - getTopToolBarHeight()
- - res.getDimensionPixelSize(R.dimen.bottom_actions_height)
- - res.getDimensionPixelSize(R.dimen.full_preview_page_default_padding_top)
- - res.getDimensionPixelSize(R.dimen.full_preview_page_default_padding_bottom);
- }
-}
diff --git a/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java b/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java
index 3e03a41c..f83da8c1 100644
--- a/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java
+++ b/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java
@@ -40,18 +40,16 @@ public class ThemedIconSectionView extends SectionView {
protected void onFinishInflate() {
super.onFinishInflate();
mSwitchView = findViewById(R.id.themed_icon_toggle);
- setOnClickListener(v -> mSwitchView.toggle());
- mSwitchView.setOnCheckedChangeListener((buttonView, isChecked) -> viewActivated(isChecked));
+ setOnClickListener(v -> {
+ mSwitchView.toggle();
+ if (mSectionViewListener != null) {
+ mSectionViewListener.onViewActivated(getContext(), mSwitchView.isChecked());
+ }
+ });
}
/** Gets the switch view. */
public Switch getSwitch() {
return mSwitchView;
}
-
- private void viewActivated(boolean isChecked) {
- if (mSectionViewListener != null) {
- mSectionViewListener.onViewActivated(getContext(), isChecked);
- }
- }
}