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