Loading packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt +31 −20 Original line number Original line Diff line number Diff line Loading @@ -18,17 +18,19 @@ package com.android.systemui.qs.external import android.content.ComponentName import android.content.ComponentName import android.content.Context import android.content.Context import android.content.SharedPreferences import android.service.quicksettings.Tile import android.service.quicksettings.Tile import android.util.Log import android.util.Log import com.android.internal.annotations.VisibleForTesting import com.android.internal.annotations.VisibleForTesting import javax.inject.Inject import org.json.JSONException import org.json.JSONException import org.json.JSONObject import org.json.JSONObject import javax.inject.Inject data class TileServiceKey(val componentName: ComponentName, val user: Int) { data class TileServiceKey(val componentName: ComponentName, val user: Int) { private val string = "${componentName.flattenToString()}:$user" private val string = "${componentName.flattenToString()}:$user" override fun toString() = string override fun toString() = string } } private const val STATE = "state" private const val STATE = "state" private const val LABEL = "label" private const val LABEL = "label" private const val SUBTITLE = "subtitle" private const val SUBTITLE = "subtitle" Loading @@ -44,12 +46,7 @@ private const val STATE_DESCRIPTION = "state_description" * It persists the state from a [Tile] necessary to present the view in the same state when * It persists the state from a [Tile] necessary to present the view in the same state when * retrieved, with the exception of the icon. * retrieved, with the exception of the icon. */ */ class CustomTileStatePersister @Inject constructor(context: Context) { interface CustomTileStatePersister { companion object { private const val FILE_NAME = "custom_tiles_state" } private val sharedPreferences = context.getSharedPreferences(FILE_NAME, 0) /** /** * Read the state from [SharedPreferences]. * Read the state from [SharedPreferences]. Loading @@ -58,7 +55,31 @@ class CustomTileStatePersister @Inject constructor(context: Context) { * * * Any fields that have not been saved will be set to `null` * Any fields that have not been saved will be set to `null` */ */ fun readState(key: TileServiceKey): Tile? { fun readState(key: TileServiceKey): Tile? /** * Persists the state into [SharedPreferences]. * * The implementation does not store fields that are `null` or icons. */ fun persistState(key: TileServiceKey, tile: Tile) /** * Removes the state for a given tile, user pair. * * Used when the tile is removed by the user. */ fun removeState(key: TileServiceKey) } // TODO(b/299909989) Merge this class into into CustomTileRepository class CustomTileStatePersisterImpl @Inject constructor(context: Context) : CustomTileStatePersister { companion object { private const val FILE_NAME = "custom_tiles_state" } private val sharedPreferences: SharedPreferences = context.getSharedPreferences(FILE_NAME, 0) override fun readState(key: TileServiceKey): Tile? { val state = sharedPreferences.getString(key.toString(), null) ?: return null val state = sharedPreferences.getString(key.toString(), null) ?: return null return try { return try { readTileFromString(state) readTileFromString(state) Loading @@ -68,23 +89,13 @@ class CustomTileStatePersister @Inject constructor(context: Context) { } } } } /** override fun persistState(key: TileServiceKey, tile: Tile) { * Persists the state into [SharedPreferences]. * * The implementation does not store fields that are `null` or icons. */ fun persistState(key: TileServiceKey, tile: Tile) { val state = writeToString(tile) val state = writeToString(tile) sharedPreferences.edit().putString(key.toString(), state).apply() sharedPreferences.edit().putString(key.toString(), state).apply() } } /** override fun removeState(key: TileServiceKey) { * Removes the state for a given tile, user pair. * * Used when the tile is removed by the user. */ fun removeState(key: TileServiceKey) { sharedPreferences.edit().remove(key.toString()).apply() sharedPreferences.edit().remove(key.toString()).apply() } } } } Loading packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt +5 −0 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles.di package com.android.systemui.qs.tiles.di import com.android.systemui.qs.external.CustomTileStatePersister import com.android.systemui.qs.external.CustomTileStatePersisterImpl import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerImpl import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerImpl import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent Loading Loading @@ -52,4 +54,7 @@ interface QSTilesModule { fun bindQSTileIntentUserInputHandler( fun bindQSTileIntentUserInputHandler( impl: QSTileIntentUserInputHandlerImpl impl: QSTileIntentUserInputHandlerImpl ): QSTileIntentUserInputHandler ): QSTileIntentUserInputHandler @Binds fun bindCustomTileStatePersister(impl: CustomTileStatePersisterImpl): CustomTileStatePersister } } packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt 0 → 100644 +50 −0 Original line number Original line Diff line number Diff line /* * 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.systemui.qs.tiles.impl.custom.commons import android.service.quicksettings.Tile fun Tile.copy(): Tile = Tile().also { it.icon = icon it.label = label it.subtitle = subtitle it.contentDescription = contentDescription it.stateDescription = stateDescription it.activityLaunchForClick = activityLaunchForClick it.state = state } fun Tile.setFrom(otherTile: Tile) { if (otherTile.icon != null) { icon = otherTile.icon } if (otherTile.customLabel != null) { label = otherTile.customLabel } if (otherTile.subtitle != null) { subtitle = otherTile.subtitle } if (otherTile.contentDescription != null) { contentDescription = otherTile.contentDescription } if (otherTile.stateDescription != null) { stateDescription = otherTile.stateDescription } activityLaunchForClick = otherTile.activityLaunchForClick state = otherTile.state } packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt 0 → 100644 +196 −0 Original line number Original line Diff line number Diff line /* * 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.systemui.qs.tiles.impl.custom.data.repository import android.graphics.drawable.Icon import android.os.UserHandle import android.service.quicksettings.Tile import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.external.CustomTileStatePersister import com.android.systemui.qs.external.TileServiceKey import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.impl.custom.commons.copy import com.android.systemui.qs.tiles.impl.custom.commons.setFrom import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults import com.android.systemui.qs.tiles.impl.di.QSTileScope import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext /** * Repository store the [Tile] associated with the custom tile. It lives on [QSTileScope] which * allows it to survive service rebinding. Given that, it provides the last received state when * connected again. */ interface CustomTileRepository { /** * Restores the [Tile] if it's [isPersistable]. Restored [Tile] will be available via [getTile] * (but there is no guarantee that restoration is synchronous) and emitted in [getTiles] for a * corresponding [user]. */ suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) /** Returns [Tile] updates for a [user]. */ fun getTiles(user: UserHandle): Flow<Tile> /** * Return current [Tile] for a [user] or null if the [user] doesn't match currently cached one. * Suspending until [getTiles] returns something is a way to wait for this to become available. * * @throws IllegalStateException when there is no current tile. */ fun getTile(user: UserHandle): Tile? /** * Updates tile with the non-null values from [newTile]. Overwrites the current cache when * [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly * loaded when the [restoreForTheUserIfNeeded]. */ suspend fun updateWithTile( user: UserHandle, newTile: Tile, isPersistable: Boolean, ) /** * Updates tile with the values from [defaults]. Overwrites the current cache when [user] * differs from the cached one. [isPersistable] tile will be persisted to be possibly loaded * when the [restoreForTheUserIfNeeded]. */ suspend fun updateWithDefaults( user: UserHandle, defaults: CustomTileDefaults, isPersistable: Boolean, ) } @QSTileScope class CustomTileRepositoryImpl @Inject constructor( private val tileSpec: TileSpec.CustomTileSpec, private val customTileStatePersister: CustomTileStatePersister, @Background private val backgroundContext: CoroutineContext, ) : CustomTileRepository { private val tileUpdateMutex = Mutex() private val tileWithUserState = MutableSharedFlow<TileWithUser>(onBufferOverflow = BufferOverflow.DROP_OLDEST, replay = 1) override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) { if (isPersistable && getCurrentTileWithUser()?.user != user) { withContext(backgroundContext) { customTileStatePersister.readState(user.getKey())?.let { updateWithTile( user, it, true, ) } } } } override fun getTiles(user: UserHandle): Flow<Tile> = tileWithUserState.filter { it.user == user }.map { it.tile } override fun getTile(user: UserHandle): Tile? { val tileWithUser = getCurrentTileWithUser() ?: throw IllegalStateException("Tile is not set") return if (tileWithUser.user == user) { tileWithUser.tile } else { null } } override suspend fun updateWithTile( user: UserHandle, newTile: Tile, isPersistable: Boolean, ) = updateTile(user, isPersistable) { setFrom(newTile) } override suspend fun updateWithDefaults( user: UserHandle, defaults: CustomTileDefaults, isPersistable: Boolean, ) { if (defaults is CustomTileDefaults.Result) { updateTile(user, isPersistable) { // Update the icon if it's not set or is the default icon. val updateIcon = (icon == null || icon.isResourceEqual(defaults.icon)) if (updateIcon) { icon = defaults.icon } setDefaultLabel(defaults.label) } } } private suspend fun updateTile( user: UserHandle, isPersistable: Boolean, update: Tile.() -> Unit ): Unit = tileUpdateMutex.withLock { val currentTileWithUser = getCurrentTileWithUser() val tileToUpdate = if (currentTileWithUser?.user == user) { currentTileWithUser.tile.copy() } else { Tile() } tileToUpdate.update() if (isPersistable) { withContext(backgroundContext) { customTileStatePersister.persistState(user.getKey(), tileToUpdate) } } tileWithUserState.tryEmit(TileWithUser(user, tileToUpdate)) } private fun getCurrentTileWithUser(): TileWithUser? = tileWithUserState.replayCache.lastOrNull() /** Compare two icons, only works for resources. */ private fun Icon.isResourceEqual(icon2: Icon?): Boolean { if (icon2 == null) { return false } if (this === icon2) { return true } if (type != Icon.TYPE_RESOURCE || icon2.type != Icon.TYPE_RESOURCE) { return false } if (resId != icon2.resId) { return false } return resPackage == icon2.resPackage } private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier) private data class TileWithUser(val user: UserHandle, val tile: Tile) } packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -24,6 +24,8 @@ import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepositoryImpl import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import dagger.Binds import dagger.Binds Loading @@ -50,4 +52,6 @@ interface CustomTileModule { fun bindCustomTileDefaultsRepository( fun bindCustomTileDefaultsRepository( impl: CustomTileDefaultsRepositoryImpl impl: CustomTileDefaultsRepositoryImpl ): CustomTileDefaultsRepository ): CustomTileDefaultsRepository @Binds fun bindCustomTileRepository(impl: CustomTileRepositoryImpl): CustomTileRepository } } Loading
packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt +31 −20 Original line number Original line Diff line number Diff line Loading @@ -18,17 +18,19 @@ package com.android.systemui.qs.external import android.content.ComponentName import android.content.ComponentName import android.content.Context import android.content.Context import android.content.SharedPreferences import android.service.quicksettings.Tile import android.service.quicksettings.Tile import android.util.Log import android.util.Log import com.android.internal.annotations.VisibleForTesting import com.android.internal.annotations.VisibleForTesting import javax.inject.Inject import org.json.JSONException import org.json.JSONException import org.json.JSONObject import org.json.JSONObject import javax.inject.Inject data class TileServiceKey(val componentName: ComponentName, val user: Int) { data class TileServiceKey(val componentName: ComponentName, val user: Int) { private val string = "${componentName.flattenToString()}:$user" private val string = "${componentName.flattenToString()}:$user" override fun toString() = string override fun toString() = string } } private const val STATE = "state" private const val STATE = "state" private const val LABEL = "label" private const val LABEL = "label" private const val SUBTITLE = "subtitle" private const val SUBTITLE = "subtitle" Loading @@ -44,12 +46,7 @@ private const val STATE_DESCRIPTION = "state_description" * It persists the state from a [Tile] necessary to present the view in the same state when * It persists the state from a [Tile] necessary to present the view in the same state when * retrieved, with the exception of the icon. * retrieved, with the exception of the icon. */ */ class CustomTileStatePersister @Inject constructor(context: Context) { interface CustomTileStatePersister { companion object { private const val FILE_NAME = "custom_tiles_state" } private val sharedPreferences = context.getSharedPreferences(FILE_NAME, 0) /** /** * Read the state from [SharedPreferences]. * Read the state from [SharedPreferences]. Loading @@ -58,7 +55,31 @@ class CustomTileStatePersister @Inject constructor(context: Context) { * * * Any fields that have not been saved will be set to `null` * Any fields that have not been saved will be set to `null` */ */ fun readState(key: TileServiceKey): Tile? { fun readState(key: TileServiceKey): Tile? /** * Persists the state into [SharedPreferences]. * * The implementation does not store fields that are `null` or icons. */ fun persistState(key: TileServiceKey, tile: Tile) /** * Removes the state for a given tile, user pair. * * Used when the tile is removed by the user. */ fun removeState(key: TileServiceKey) } // TODO(b/299909989) Merge this class into into CustomTileRepository class CustomTileStatePersisterImpl @Inject constructor(context: Context) : CustomTileStatePersister { companion object { private const val FILE_NAME = "custom_tiles_state" } private val sharedPreferences: SharedPreferences = context.getSharedPreferences(FILE_NAME, 0) override fun readState(key: TileServiceKey): Tile? { val state = sharedPreferences.getString(key.toString(), null) ?: return null val state = sharedPreferences.getString(key.toString(), null) ?: return null return try { return try { readTileFromString(state) readTileFromString(state) Loading @@ -68,23 +89,13 @@ class CustomTileStatePersister @Inject constructor(context: Context) { } } } } /** override fun persistState(key: TileServiceKey, tile: Tile) { * Persists the state into [SharedPreferences]. * * The implementation does not store fields that are `null` or icons. */ fun persistState(key: TileServiceKey, tile: Tile) { val state = writeToString(tile) val state = writeToString(tile) sharedPreferences.edit().putString(key.toString(), state).apply() sharedPreferences.edit().putString(key.toString(), state).apply() } } /** override fun removeState(key: TileServiceKey) { * Removes the state for a given tile, user pair. * * Used when the tile is removed by the user. */ fun removeState(key: TileServiceKey) { sharedPreferences.edit().remove(key.toString()).apply() sharedPreferences.edit().remove(key.toString()).apply() } } } } Loading
packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt +5 −0 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles.di package com.android.systemui.qs.tiles.di import com.android.systemui.qs.external.CustomTileStatePersister import com.android.systemui.qs.external.CustomTileStatePersisterImpl import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerImpl import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerImpl import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent Loading Loading @@ -52,4 +54,7 @@ interface QSTilesModule { fun bindQSTileIntentUserInputHandler( fun bindQSTileIntentUserInputHandler( impl: QSTileIntentUserInputHandlerImpl impl: QSTileIntentUserInputHandlerImpl ): QSTileIntentUserInputHandler ): QSTileIntentUserInputHandler @Binds fun bindCustomTileStatePersister(impl: CustomTileStatePersisterImpl): CustomTileStatePersister } }
packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt 0 → 100644 +50 −0 Original line number Original line Diff line number Diff line /* * 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.systemui.qs.tiles.impl.custom.commons import android.service.quicksettings.Tile fun Tile.copy(): Tile = Tile().also { it.icon = icon it.label = label it.subtitle = subtitle it.contentDescription = contentDescription it.stateDescription = stateDescription it.activityLaunchForClick = activityLaunchForClick it.state = state } fun Tile.setFrom(otherTile: Tile) { if (otherTile.icon != null) { icon = otherTile.icon } if (otherTile.customLabel != null) { label = otherTile.customLabel } if (otherTile.subtitle != null) { subtitle = otherTile.subtitle } if (otherTile.contentDescription != null) { contentDescription = otherTile.contentDescription } if (otherTile.stateDescription != null) { stateDescription = otherTile.stateDescription } activityLaunchForClick = otherTile.activityLaunchForClick state = otherTile.state }
packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt 0 → 100644 +196 −0 Original line number Original line Diff line number Diff line /* * 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.systemui.qs.tiles.impl.custom.data.repository import android.graphics.drawable.Icon import android.os.UserHandle import android.service.quicksettings.Tile import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.external.CustomTileStatePersister import com.android.systemui.qs.external.TileServiceKey import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.impl.custom.commons.copy import com.android.systemui.qs.tiles.impl.custom.commons.setFrom import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults import com.android.systemui.qs.tiles.impl.di.QSTileScope import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext /** * Repository store the [Tile] associated with the custom tile. It lives on [QSTileScope] which * allows it to survive service rebinding. Given that, it provides the last received state when * connected again. */ interface CustomTileRepository { /** * Restores the [Tile] if it's [isPersistable]. Restored [Tile] will be available via [getTile] * (but there is no guarantee that restoration is synchronous) and emitted in [getTiles] for a * corresponding [user]. */ suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) /** Returns [Tile] updates for a [user]. */ fun getTiles(user: UserHandle): Flow<Tile> /** * Return current [Tile] for a [user] or null if the [user] doesn't match currently cached one. * Suspending until [getTiles] returns something is a way to wait for this to become available. * * @throws IllegalStateException when there is no current tile. */ fun getTile(user: UserHandle): Tile? /** * Updates tile with the non-null values from [newTile]. Overwrites the current cache when * [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly * loaded when the [restoreForTheUserIfNeeded]. */ suspend fun updateWithTile( user: UserHandle, newTile: Tile, isPersistable: Boolean, ) /** * Updates tile with the values from [defaults]. Overwrites the current cache when [user] * differs from the cached one. [isPersistable] tile will be persisted to be possibly loaded * when the [restoreForTheUserIfNeeded]. */ suspend fun updateWithDefaults( user: UserHandle, defaults: CustomTileDefaults, isPersistable: Boolean, ) } @QSTileScope class CustomTileRepositoryImpl @Inject constructor( private val tileSpec: TileSpec.CustomTileSpec, private val customTileStatePersister: CustomTileStatePersister, @Background private val backgroundContext: CoroutineContext, ) : CustomTileRepository { private val tileUpdateMutex = Mutex() private val tileWithUserState = MutableSharedFlow<TileWithUser>(onBufferOverflow = BufferOverflow.DROP_OLDEST, replay = 1) override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) { if (isPersistable && getCurrentTileWithUser()?.user != user) { withContext(backgroundContext) { customTileStatePersister.readState(user.getKey())?.let { updateWithTile( user, it, true, ) } } } } override fun getTiles(user: UserHandle): Flow<Tile> = tileWithUserState.filter { it.user == user }.map { it.tile } override fun getTile(user: UserHandle): Tile? { val tileWithUser = getCurrentTileWithUser() ?: throw IllegalStateException("Tile is not set") return if (tileWithUser.user == user) { tileWithUser.tile } else { null } } override suspend fun updateWithTile( user: UserHandle, newTile: Tile, isPersistable: Boolean, ) = updateTile(user, isPersistable) { setFrom(newTile) } override suspend fun updateWithDefaults( user: UserHandle, defaults: CustomTileDefaults, isPersistable: Boolean, ) { if (defaults is CustomTileDefaults.Result) { updateTile(user, isPersistable) { // Update the icon if it's not set or is the default icon. val updateIcon = (icon == null || icon.isResourceEqual(defaults.icon)) if (updateIcon) { icon = defaults.icon } setDefaultLabel(defaults.label) } } } private suspend fun updateTile( user: UserHandle, isPersistable: Boolean, update: Tile.() -> Unit ): Unit = tileUpdateMutex.withLock { val currentTileWithUser = getCurrentTileWithUser() val tileToUpdate = if (currentTileWithUser?.user == user) { currentTileWithUser.tile.copy() } else { Tile() } tileToUpdate.update() if (isPersistable) { withContext(backgroundContext) { customTileStatePersister.persistState(user.getKey(), tileToUpdate) } } tileWithUserState.tryEmit(TileWithUser(user, tileToUpdate)) } private fun getCurrentTileWithUser(): TileWithUser? = tileWithUserState.replayCache.lastOrNull() /** Compare two icons, only works for resources. */ private fun Icon.isResourceEqual(icon2: Icon?): Boolean { if (icon2 == null) { return false } if (this === icon2) { return true } if (type != Icon.TYPE_RESOURCE || icon2.type != Icon.TYPE_RESOURCE) { return false } if (resId != icon2.resId) { return false } return resPackage == icon2.resPackage } private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier) private data class TileWithUser(val user: UserHandle, val tile: Tile) }
packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -24,6 +24,8 @@ import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepositoryImpl import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import dagger.Binds import dagger.Binds Loading @@ -50,4 +52,6 @@ interface CustomTileModule { fun bindCustomTileDefaultsRepository( fun bindCustomTileDefaultsRepository( impl: CustomTileDefaultsRepositoryImpl impl: CustomTileDefaultsRepositoryImpl ): CustomTileDefaultsRepository ): CustomTileDefaultsRepository @Binds fun bindCustomTileRepository(impl: CustomTileRepositoryImpl): CustomTileRepository } }