1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.communal.data.db 18 19 import android.content.ComponentName 20 import androidx.room.Dao 21 import androidx.room.Delete 22 import androidx.room.Query 23 import androidx.room.RoomDatabase 24 import androidx.room.Transaction 25 import androidx.sqlite.db.SupportSQLiteDatabase 26 import com.android.systemui.communal.nano.CommunalHubState 27 import com.android.systemui.communal.widgets.CommunalWidgetHost 28 import com.android.systemui.communal.widgets.CommunalWidgetModule.Companion.DEFAULT_WIDGETS 29 import com.android.systemui.dagger.qualifiers.Application 30 import com.android.systemui.dagger.qualifiers.Background 31 import com.android.systemui.log.LogBuffer 32 import com.android.systemui.log.core.Logger 33 import com.android.systemui.log.dagger.CommunalLog 34 import javax.inject.Inject 35 import javax.inject.Named 36 import javax.inject.Provider 37 import kotlinx.coroutines.CoroutineDispatcher 38 import kotlinx.coroutines.CoroutineScope 39 import kotlinx.coroutines.flow.Flow 40 import kotlinx.coroutines.launch 41 import kotlinx.coroutines.withContext 42 43 /** 44 * Callback that will be invoked when the Room database is created. Then the database will be 45 * populated with pre-configured default widgets to be rendered in the glanceable hub. 46 */ 47 class DefaultWidgetPopulation 48 @Inject 49 constructor( 50 @Application private val applicationScope: CoroutineScope, 51 @Background private val bgDispatcher: CoroutineDispatcher, 52 private val communalWidgetHost: CommunalWidgetHost, 53 private val communalWidgetDaoProvider: Provider<CommunalWidgetDao>, 54 @Named(DEFAULT_WIDGETS) private val defaultWidgets: Array<String>, 55 @CommunalLog logBuffer: LogBuffer, 56 ) : RoomDatabase.Callback() { 57 companion object { 58 private const val TAG = "DefaultWidgetPopulation" 59 } 60 private val logger = Logger(logBuffer, TAG) 61 62 override fun onCreate(db: SupportSQLiteDatabase) { 63 super.onCreate(db) 64 applicationScope.launch { 65 addDefaultWidgets() 66 logger.i("Default widgets were populated in the database.") 67 } 68 } 69 70 // Read default widgets from config.xml and populate the database. 71 private suspend fun addDefaultWidgets() = 72 withContext(bgDispatcher) { 73 defaultWidgets.forEachIndexed { index, name -> 74 val provider = ComponentName.unflattenFromString(name) 75 provider?.let { 76 val id = communalWidgetHost.allocateIdAndBindWidget(provider) 77 id?.let { 78 communalWidgetDaoProvider 79 .get() 80 .addWidget( 81 widgetId = id, 82 provider = provider, 83 priority = defaultWidgets.size - index 84 ) 85 } 86 } 87 } 88 } 89 } 90 91 @Dao 92 interface CommunalWidgetDao { 93 @Query( 94 "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " + 95 "ON communal_item_rank_table.uid = communal_widget_table.item_id " + 96 "ORDER BY communal_item_rank_table.rank DESC" 97 ) getWidgetsnull98 fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>> 99 100 @Query("SELECT * FROM communal_widget_table WHERE widget_id = :id") 101 fun getWidgetByIdNow(id: Int): CommunalWidgetItem? 102 103 @Delete fun deleteWidgets(vararg widgets: CommunalWidgetItem) 104 105 @Query("DELETE FROM communal_item_rank_table WHERE uid = :itemId") 106 fun deleteItemRankById(itemId: Long) 107 108 @Query( 109 "INSERT INTO communal_widget_table(widget_id, component_name, item_id) " + 110 "VALUES(:widgetId, :componentName, :itemId)" 111 ) 112 fun insertWidget(widgetId: Int, componentName: String, itemId: Long): Long 113 114 @Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)") 115 fun insertItemRank(rank: Int): Long 116 117 @Query("UPDATE communal_item_rank_table SET rank = :order WHERE uid = :itemUid") 118 fun updateItemRank(itemUid: Long, order: Int) 119 120 @Query("DELETE FROM communal_widget_table") fun clearCommunalWidgetsTable() 121 122 @Query("DELETE FROM communal_item_rank_table") fun clearCommunalItemRankTable() 123 124 @Transaction 125 fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) { 126 widgetIdToPriorityMap.forEach { (id, priority) -> 127 val widget = getWidgetByIdNow(id) 128 if (widget != null) { 129 updateItemRank(widget.itemId, priority) 130 } 131 } 132 } 133 134 @Transaction addWidgetnull135 fun addWidget(widgetId: Int, provider: ComponentName, priority: Int): Long { 136 return addWidget( 137 widgetId = widgetId, 138 componentName = provider.flattenToString(), 139 priority = priority, 140 ) 141 } 142 143 @Transaction addWidgetnull144 fun addWidget(widgetId: Int, componentName: String, priority: Int): Long { 145 return insertWidget( 146 widgetId = widgetId, 147 componentName = componentName, 148 itemId = insertItemRank(priority), 149 ) 150 } 151 152 @Transaction deleteWidgetByIdnull153 fun deleteWidgetById(widgetId: Int): Boolean { 154 val widget = 155 getWidgetByIdNow(widgetId) ?: // no entry to delete from db 156 return false 157 158 deleteItemRankById(widget.itemId) 159 deleteWidgets(widget) 160 return true 161 } 162 163 /** Wipes current database and restores the snapshot represented by [state]. */ 164 @Transaction restoreCommunalHubStatenull165 fun restoreCommunalHubState(state: CommunalHubState) { 166 clearCommunalWidgetsTable() 167 clearCommunalItemRankTable() 168 169 state.widgets.forEach { addWidget(it.widgetId, it.componentName, it.rank) } 170 } 171 } 172