summaryrefslogtreecommitdiff
path: root/src/com/android/customization/picker/clock/data
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/customization/picker/clock/data')
-rw-r--r--src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt52
-rw-r--r--src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt193
-rw-r--r--src/com/android/customization/picker/clock/data/repository/ClockRegistryProvider.kt121
3 files changed, 366 insertions, 0 deletions
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
new file mode 100644
index 00000000..ae66ce3d
--- /dev/null
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.data.repository
+
+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 kotlinx.coroutines.flow.Flow
+
+/**
+ * Repository for accessing application clock settings, as well as selecting and configuring custom
+ * clocks.
+ */
+interface ClockPickerRepository {
+ val allClocks: Flow<List<ClockMetadataModel>>
+
+ val selectedClock: Flow<ClockMetadataModel>
+
+ val selectedClockSize: Flow<ClockSize>
+
+ fun setSelectedClock(clockId: String)
+
+ /**
+ * Set clock color to the settings.
+ *
+ * @param selectedColor selected color in the color option list.
+ * @param colorToneProgress color tone from 0 to 100 to apply to the selected color
+ * @param seedColor the actual clock color after blending the selected color and color tone
+ */
+ fun setClockColor(
+ selectedColorId: String?,
+ @IntRange(from = 0, to = 100) colorToneProgress: Int,
+ @ColorInt seedColor: Int?,
+ )
+
+ suspend fun setClockSize(size: ClockSize)
+}
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
new file mode 100644
index 00000000..880a00be
--- /dev/null
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.data.repository
+
+import android.provider.Settings
+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.shared.clocks.ClockRegistry
+import com.android.wallpaper.settings.data.repository.SecureSettingsRepository
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.shareIn
+import org.json.JSONObject
+
+/** Implementation of [ClockPickerRepository], using [ClockRegistry]. */
+class ClockPickerRepositoryImpl(
+ private val secureSettingsRepository: SecureSettingsRepository,
+ private val registry: ClockRegistry,
+ scope: CoroutineScope,
+) : ClockPickerRepository {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override val allClocks: Flow<List<ClockMetadataModel>> =
+ callbackFlow {
+ fun send() {
+ val allClocks =
+ registry
+ .getClocks()
+ .filter { "NOT_IN_USE" !in it.clockId }
+ .map { it.toModel() }
+ trySend(allClocks)
+ }
+
+ val listener =
+ object : ClockRegistry.ClockChangeListener {
+ override fun onAvailableClocksChanged() {
+ send()
+ }
+ }
+ registry.registerClockChangeListener(listener)
+ send()
+ awaitClose { registry.unregisterClockChangeListener(listener) }
+ }
+ .mapLatest { allClocks ->
+ // Loading list of clock plugins can cause many consecutive calls of
+ // onAvailableClocksChanged(). We only care about the final fully-initiated clock
+ // list. Delay to avoid unnecessary too many emits.
+ delay(100)
+ allClocks
+ }
+
+ /** The currently-selected clock. This also emits the clock color information. */
+ override val selectedClock: Flow<ClockMetadataModel> =
+ callbackFlow {
+ fun send() {
+ val currentClockId = registry.currentClockId
+ val metadata = registry.settings?.metadata
+ val model =
+ registry
+ .getClocks()
+ .find { clockMetadata -> clockMetadata.clockId == currentClockId }
+ ?.toModel(
+ selectedColorId = metadata?.getSelectedColorId(),
+ colorTone = metadata?.getColorTone()
+ ?: ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
+ seedColor = registry.seedColor
+ )
+ trySend(model)
+ }
+
+ val listener =
+ object : ClockRegistry.ClockChangeListener {
+ override fun onCurrentClockChanged() {
+ send()
+ }
+
+ override fun onAvailableClocksChanged() {
+ send()
+ }
+ }
+ registry.registerClockChangeListener(listener)
+ send()
+ awaitClose { registry.unregisterClockChangeListener(listener) }
+ }
+ .mapNotNull { it }
+
+ override fun setSelectedClock(clockId: String) {
+ registry.mutateSetting { oldSettings ->
+ val newSettings = oldSettings.copy(clockId = clockId)
+ newSettings.metadata = oldSettings.metadata
+ newSettings
+ }
+ }
+
+ override fun setClockColor(
+ selectedColorId: String?,
+ @IntRange(from = 0, to = 100) colorToneProgress: Int,
+ @ColorInt seedColor: Int?,
+ ) {
+ registry.mutateSetting { oldSettings ->
+ val newSettings = oldSettings.copy(seedColor = seedColor)
+ newSettings.metadata =
+ oldSettings.metadata
+ .put(KEY_METADATA_SELECTED_COLOR_ID, selectedColorId)
+ .put(KEY_METADATA_COLOR_TONE_PROGRESS, colorToneProgress)
+ newSettings
+ }
+ }
+
+ override val selectedClockSize: SharedFlow<ClockSize> =
+ secureSettingsRepository
+ .intSetting(
+ name = Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
+ )
+ .map { setting -> setting == 1 }
+ .map { isDynamic -> if (isDynamic) ClockSize.DYNAMIC else ClockSize.SMALL }
+ .shareIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
+
+ override suspend fun setClockSize(size: ClockSize) {
+ secureSettingsRepository.set(
+ name = Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
+ value = if (size == ClockSize.DYNAMIC) 1 else 0,
+ )
+ }
+
+ private fun JSONObject.getSelectedColorId(): String? {
+ return if (this.isNull(KEY_METADATA_SELECTED_COLOR_ID)) {
+ null
+ } else {
+ this.getString(KEY_METADATA_SELECTED_COLOR_ID)
+ }
+ }
+
+ private fun JSONObject.getColorTone(): Int {
+ return this.optInt(
+ KEY_METADATA_COLOR_TONE_PROGRESS,
+ ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS
+ )
+ }
+
+ /** By default, [ClockMetadataModel] has no color information unless specified. */
+ private fun ClockMetadata.toModel(
+ selectedColorId: String? = null,
+ @IntRange(from = 0, to = 100) colorTone: Int = 0,
+ @ColorInt seedColor: Int? = null,
+ ): ClockMetadataModel {
+ return ClockMetadataModel(
+ clockId = clockId,
+ name = name,
+ selectedColorId = selectedColorId,
+ colorToneProgress = colorTone,
+ seedColor = seedColor,
+ )
+ }
+
+ companion object {
+ // The selected color in the color option list
+ private const val KEY_METADATA_SELECTED_COLOR_ID = "metadataSelectedColorId"
+
+ // The color tone to apply to the selected color
+ private const val KEY_METADATA_COLOR_TONE_PROGRESS = "metadataColorToneProgress"
+ }
+}
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockRegistryProvider.kt b/src/com/android/customization/picker/clock/data/repository/ClockRegistryProvider.kt
new file mode 100644
index 00000000..bfe87c9c
--- /dev/null
+++ b/src/com/android/customization/picker/clock/data/repository/ClockRegistryProvider.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.data.repository
+
+import android.app.NotificationManager
+import android.content.ComponentName
+import android.content.Context
+import android.view.LayoutInflater
+import com.android.systemui.plugins.Plugin
+import com.android.systemui.plugins.PluginManager
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.shared.clocks.DefaultClockProvider
+import com.android.systemui.shared.plugins.PluginActionManager
+import com.android.systemui.shared.plugins.PluginEnabler
+import com.android.systemui.shared.plugins.PluginInstance
+import com.android.systemui.shared.plugins.PluginManagerImpl
+import com.android.systemui.shared.plugins.PluginPrefs
+import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager_Factory
+import java.util.concurrent.Executors
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Provide the [ClockRegistry] singleton. Note that we need to make sure that the [PluginManager]
+ * needs to be connected before [ClockRegistry] is ready to use.
+ */
+class ClockRegistryProvider(
+ private val context: Context,
+ private val coroutineScope: CoroutineScope,
+ private val mainDispatcher: CoroutineDispatcher,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ private val pluginManager: PluginManager by lazy { createPluginManager(context) }
+ private val clockRegistry: ClockRegistry by lazy {
+ ClockRegistry(
+ context,
+ pluginManager,
+ coroutineScope,
+ mainDispatcher,
+ backgroundDispatcher,
+ isEnabled = true,
+ handleAllUsers = false,
+ DefaultClockProvider(context, LayoutInflater.from(context), context.resources)
+ )
+ .apply { registerListeners() }
+ }
+
+ fun get(): ClockRegistry {
+ return clockRegistry
+ }
+
+ private fun createPluginManager(context: Context): PluginManager {
+ val privilegedPlugins = listOf<String>()
+ val isDebugDevice = true
+
+ val instanceFactory =
+ PluginInstance.Factory(
+ this::class.java.classLoader,
+ PluginInstance.InstanceFactory<Plugin>(),
+ PluginInstance.VersionChecker(),
+ privilegedPlugins,
+ isDebugDevice,
+ )
+
+ /*
+ * let SystemUI handle plugin, in this class assume plugins are enabled
+ */
+ val pluginEnabler =
+ object : PluginEnabler {
+ override fun setEnabled(component: ComponentName) = Unit
+
+ override fun setDisabled(
+ component: ComponentName,
+ @PluginEnabler.DisableReason reason: Int
+ ) = Unit
+
+ override fun isEnabled(component: ComponentName): Boolean {
+ return true
+ }
+
+ @PluginEnabler.DisableReason
+ override fun getDisableReason(componentName: ComponentName): Int {
+ return PluginEnabler.ENABLED
+ }
+ }
+
+ val pluginActionManager =
+ PluginActionManager.Factory(
+ context,
+ context.packageManager,
+ context.mainExecutor,
+ Executors.newSingleThreadExecutor(),
+ context.getSystemService(NotificationManager::class.java),
+ pluginEnabler,
+ privilegedPlugins,
+ instanceFactory,
+ )
+ return PluginManagerImpl(
+ context,
+ pluginActionManager,
+ isDebugDevice,
+ UncaughtExceptionPreHandlerManager_Factory.create().get(),
+ pluginEnabler,
+ PluginPrefs(context),
+ listOf(),
+ )
+ }
+}