blob: 390f7064a22710239a2ac76b3d53e4292b301e68 [file] [log] [blame]
Pinyao Tingee191b12020-04-29 18:35:39 -07001/*
2 * Copyright (C) 2020 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 */
16package com.android.systemui.bubbles
17
Pinyao Tingd4c1ba92020-05-04 20:29:56 -070018import android.annotation.SuppressLint
Pinyao Tingee191b12020-04-29 18:35:39 -070019import android.annotation.UserIdInt
Pinyao Tingd4c1ba92020-05-04 20:29:56 -070020import android.content.pm.LauncherApps
21import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
22import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
23import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
24import android.os.UserHandle
Pinyao Ting76ed7ba2020-04-29 18:35:39 -070025import android.util.Log
Pinyao Tingd4c1ba92020-05-04 20:29:56 -070026import com.android.systemui.bubbles.storage.BubbleEntity
Pinyao Tingee191b12020-04-29 18:35:39 -070027import com.android.systemui.bubbles.storage.BubblePersistentRepository
28import com.android.systemui.bubbles.storage.BubbleVolatileRepository
Pinyao Tingee191b12020-04-29 18:35:39 -070029import kotlinx.coroutines.CoroutineScope
30import kotlinx.coroutines.Dispatchers
31import kotlinx.coroutines.Job
32import kotlinx.coroutines.cancelAndJoin
33import kotlinx.coroutines.launch
34import kotlinx.coroutines.yield
Pinyao Tingd4c1ba92020-05-04 20:29:56 -070035
Pinyao Tingee191b12020-04-29 18:35:39 -070036import javax.inject.Inject
37import javax.inject.Singleton
38
39@Singleton
40internal class BubbleDataRepository @Inject constructor(
41 private val volatileRepository: BubbleVolatileRepository,
Pinyao Tingd4c1ba92020-05-04 20:29:56 -070042 private val persistentRepository: BubblePersistentRepository,
43 private val launcherApps: LauncherApps
Pinyao Tingee191b12020-04-29 18:35:39 -070044) {
45
46 private val ioScope = CoroutineScope(Dispatchers.IO)
Pinyao Ting76ed7ba2020-04-29 18:35:39 -070047 private val uiScope = CoroutineScope(Dispatchers.Main)
Pinyao Tingee191b12020-04-29 18:35:39 -070048 private var job: Job? = null
49
50 /**
51 * Adds the bubble in memory, then persists the snapshot after adding the bubble to disk
52 * asynchronously.
53 */
Pinyao Ting76ed7ba2020-04-29 18:35:39 -070054 fun addBubble(@UserIdInt userId: Int, bubble: Bubble) = addBubbles(userId, listOf(bubble))
Pinyao Tingee191b12020-04-29 18:35:39 -070055
56 /**
57 * Adds the bubble in memory, then persists the snapshot after adding the bubble to disk
58 * asynchronously.
59 */
60 fun addBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) {
Pinyao Ting76ed7ba2020-04-29 18:35:39 -070061 if (DEBUG) Log.d(TAG, "adding ${bubbles.size} bubbles")
62 val entities = transform(userId, bubbles).also(volatileRepository::addBubbles)
63 if (entities.isNotEmpty()) persistToDisk()
Pinyao Tingee191b12020-04-29 18:35:39 -070064 }
65
Pinyao Ting76ed7ba2020-04-29 18:35:39 -070066 /**
67 * Removes the bubbles from memory, then persists the snapshot to disk asynchronously.
68 */
Pinyao Tingee191b12020-04-29 18:35:39 -070069 fun removeBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) {
Pinyao Ting76ed7ba2020-04-29 18:35:39 -070070 if (DEBUG) Log.d(TAG, "removing ${bubbles.size} bubbles")
71 val entities = transform(userId, bubbles).also(volatileRepository::removeBubbles)
72 if (entities.isNotEmpty()) persistToDisk()
73 }
74
Pinyao Tingd4c1ba92020-05-04 20:29:56 -070075 private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> {
Pinyao Ting76ed7ba2020-04-29 18:35:39 -070076 return bubbles.mapNotNull { b ->
Pinyao Ting175a5b82020-06-15 23:41:14 +000077 BubbleEntity(
78 userId,
79 b.packageName,
80 b.shortcutInfo?.id ?: return@mapNotNull null,
81 b.key,
82 b.rawDesiredHeight,
83 b.rawDesiredHeightResId,
84 b.title
85 )
Pinyao Ting76ed7ba2020-04-29 18:35:39 -070086 }
Pinyao Tingee191b12020-04-29 18:35:39 -070087 }
88
89 /**
90 * Persists the bubbles to disk. When being called multiple times, it waits for first ongoing
91 * write operation to finish then run another write operation exactly once.
92 *
93 * e.g.
94 * Job A started -> blocking I/O
95 * Job B started, cancels A, wait for blocking I/O in A finishes
96 * Job C started, cancels B, wait for job B to finish
97 * Job D started, cancels C, wait for job C to finish
98 * Job A completed
99 * Job B resumes and reaches yield() and is then cancelled
100 * Job C resumes and reaches yield() and is then cancelled
101 * Job D resumes and performs another blocking I/O
102 */
103 private fun persistToDisk() {
104 val prev = job
105 job = ioScope.launch {
106 // if there was an ongoing disk I/O operation, they can be cancelled
107 prev?.cancelAndJoin()
108 // check for cancellation before disk I/O
109 yield()
110 // save to disk
111 persistentRepository.persistsToDisk(volatileRepository.bubbles)
112 }
113 }
Pinyao Ting76ed7ba2020-04-29 18:35:39 -0700114
115 /**
116 * Load bubbles from disk.
117 */
Pinyao Tingd4c1ba92020-05-04 20:29:56 -0700118 @SuppressLint("WrongConstant")
Pinyao Ting76ed7ba2020-04-29 18:35:39 -0700119 fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch {
Pinyao Tingd4c1ba92020-05-04 20:29:56 -0700120 /**
121 * Load BubbleEntity from disk.
122 * e.g.
123 * [
124 * BubbleEntity(0, "com.example.messenger", "id-2"),
125 * BubbleEntity(10, "com.example.chat", "my-id1")
126 * BubbleEntity(0, "com.example.messenger", "id-1")
127 * ]
128 */
129 val entities = persistentRepository.readFromDisk()
130 volatileRepository.addBubbles(entities)
131 /**
132 * Extract userId/packageName from these entities.
133 * e.g.
134 * [
135 * ShortcutKey(0, "com.example.messenger"), ShortcutKey(0, "com.example.chat")
136 * ]
137 */
138 val shortcutKeys = entities.map { ShortcutKey(it.userId, it.packageName) }.toSet()
139 /**
140 * Retrieve shortcuts with given userId/packageName combination, then construct a mapping
Pinyao Ting6a8fab02020-05-21 14:30:21 -0700141 * from the userId/packageName pair to a list of associated ShortcutInfo.
Pinyao Tingd4c1ba92020-05-04 20:29:56 -0700142 * e.g.
143 * {
Pinyao Ting6a8fab02020-05-21 14:30:21 -0700144 * ShortcutKey(0, "com.example.messenger") -> [
Pinyao Tingd4c1ba92020-05-04 20:29:56 -0700145 * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-0"),
Pinyao Ting6a8fab02020-05-21 14:30:21 -0700146 * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-2")
147 * ]
148 * ShortcutKey(10, "com.example.chat") -> [
Pinyao Tingd4c1ba92020-05-04 20:29:56 -0700149 * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-1"),
Pinyao Tingd4c1ba92020-05-04 20:29:56 -0700150 * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-3")
Pinyao Ting6a8fab02020-05-21 14:30:21 -0700151 * ]
Pinyao Tingd4c1ba92020-05-04 20:29:56 -0700152 * }
153 */
154 val shortcutMap = shortcutKeys.flatMap { key ->
155 launcherApps.getShortcuts(
156 LauncherApps.ShortcutQuery()
157 .setPackage(key.pkg)
158 .setQueryFlags(SHORTCUT_QUERY_FLAG), UserHandle.of(key.userId))
Pinyao Ting6a8fab02020-05-21 14:30:21 -0700159 ?: emptyList()
160 }.groupBy { ShortcutKey(it.userId, it.`package`) }
Pinyao Tingd4c1ba92020-05-04 20:29:56 -0700161 // For each entity loaded from xml, find the corresponding ShortcutInfo then convert them
162 // into Bubble.
Pinyao Ting6a8fab02020-05-21 14:30:21 -0700163 val bubbles = entities.mapNotNull { entity ->
164 shortcutMap[ShortcutKey(entity.userId, entity.packageName)]
Pinyao Ting4ddd7782020-06-19 11:37:08 -0700165 ?.firstOrNull { shortcutInfo -> entity.shortcutId == shortcutInfo.id }
Pinyao Ting175a5b82020-06-15 23:41:14 +0000166 ?.let { shortcutInfo -> Bubble(
167 entity.key,
168 shortcutInfo,
169 entity.desiredHeight,
170 entity.desiredHeightResId,
171 entity.title
172 ) }
Pinyao Ting6a8fab02020-05-21 14:30:21 -0700173 }
Pinyao Tingd4c1ba92020-05-04 20:29:56 -0700174 uiScope.launch { cb(bubbles) }
Pinyao Ting76ed7ba2020-04-29 18:35:39 -0700175 }
Pinyao Tingee191b12020-04-29 18:35:39 -0700176}
Pinyao Ting76ed7ba2020-04-29 18:35:39 -0700177
Pinyao Ting92091b12020-05-27 16:54:50 -0700178data class ShortcutKey(val userId: Int, val pkg: String)
179
Pinyao Ting76ed7ba2020-04-29 18:35:39 -0700180private const val TAG = "BubbleDataRepository"
181private const val DEBUG = false
Pinyao Tingd4c1ba92020-05-04 20:29:56 -0700182private const val SHORTCUT_QUERY_FLAG =
183 FLAG_MATCH_DYNAMIC or FLAG_MATCH_PINNED_BY_ANY_LAUNCHER or FLAG_MATCH_CACHED