1 /* <lambda>null2 * 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 */ 16 package com.android.wm.shell.bubbles.storage 17 18 import android.annotation.UserIdInt 19 import android.content.pm.LauncherApps 20 import android.os.UserHandle 21 import android.util.SparseArray 22 import com.android.internal.annotations.VisibleForTesting 23 import com.android.wm.shell.bubbles.ShortcutKey 24 25 private const val CAPACITY = 16 26 27 /** 28 * BubbleVolatileRepository holds the most updated snapshot of list of bubbles for in-memory 29 * manipulation. 30 */ 31 class BubbleVolatileRepository(private val launcherApps: LauncherApps) { 32 33 /** 34 * Set of bubbles per user. Each set of bubbles is ordered by recency. 35 */ 36 private var entitiesByUser = SparseArray<MutableList<BubbleEntity>>() 37 38 /** 39 * The capacity of the cache. 40 */ 41 @VisibleForTesting 42 var capacity = CAPACITY 43 44 /** 45 * Returns a snapshot of all the bubbles, a map of the userId to bubble list. 46 */ 47 val bubbles: SparseArray<List<BubbleEntity>> 48 @Synchronized 49 get() { 50 val map = SparseArray<List<BubbleEntity>>() 51 for (i in 0 until entitiesByUser.size()) { 52 val k = entitiesByUser.keyAt(i) 53 val v = entitiesByUser.valueAt(i) 54 map.put(k, v.toList()) 55 } 56 return map 57 } 58 59 /** 60 * Returns the entity list of the provided user's bubbles or creates one if it doesn't exist. 61 */ 62 @Synchronized 63 fun getEntities(userId: Int): MutableList<BubbleEntity> { 64 val entities = entitiesByUser.get(userId) 65 return when (entities) { 66 null -> mutableListOf<BubbleEntity>().also { 67 entitiesByUser.put(userId, it) 68 } 69 else -> entities 70 } 71 } 72 73 /** 74 * Add the bubbles to memory and perform a de-duplication. In case a bubble already exists, 75 * it will be moved to the last. 76 */ 77 @Synchronized 78 fun addBubbles(userId: Int, bubbles: List<BubbleEntity>) { 79 if (bubbles.isEmpty()) return 80 // Get the list for this user 81 var entities = getEntities(userId) 82 // Verify the size of given bubbles is within capacity, otherwise trim down to capacity 83 val bubblesInRange = bubbles.takeLast(capacity) 84 // To ensure natural ordering of the bubbles, removes bubbles which already exist 85 val uniqueBubbles = bubblesInRange.filterNot { b: BubbleEntity -> 86 entities.removeIf { e: BubbleEntity -> b.key == e.key } } 87 val overflowCount = entities.size + bubblesInRange.size - capacity 88 if (overflowCount > 0) { 89 // Uncache ShortcutInfo of bubbles that will be removed due to capacity 90 uncache(entities.take(overflowCount)) 91 entities = entities.drop(overflowCount).toMutableList() 92 } 93 entities.addAll(bubblesInRange) 94 entitiesByUser.put(userId, entities) 95 cache(uniqueBubbles) 96 } 97 98 @Synchronized 99 fun removeBubbles(@UserIdInt userId: Int, bubbles: List<BubbleEntity>) = 100 uncache(bubbles.filter { b: BubbleEntity -> 101 getEntities(userId).removeIf { e: BubbleEntity -> b.key == e.key } }) 102 103 /** 104 * Removes all the bubbles associated with the provided userId. 105 * @return whether bubbles were removed or not. 106 */ 107 @Synchronized 108 fun removeBubblesForUser(@UserIdInt userId: Int, @UserIdInt parentUserId: Int): Boolean { 109 if (parentUserId != -1) { 110 return removeBubblesForUserWithParent(userId, parentUserId) 111 } else { 112 val entities = entitiesByUser.get(userId) 113 entitiesByUser.remove(userId) 114 return entities != null 115 } 116 } 117 118 /** 119 * Removes all the bubbles associated with the provided userId when that userId is part of 120 * a profile (e.g. managed account). 121 * 122 * @return whether bubbles were removed or not. 123 */ 124 @Synchronized 125 private fun removeBubblesForUserWithParent( 126 @UserIdInt userId: Int, 127 @UserIdInt parentUserId: Int 128 ): Boolean { 129 if (entitiesByUser.get(parentUserId) != null) { 130 return entitiesByUser.get(parentUserId).removeIf { 131 b: BubbleEntity -> b.userId == userId } 132 } 133 return false 134 } 135 136 /** 137 * Goes through all the persisted bubbles and removes them if the user is not in the active 138 * list of users. 139 * 140 * @return whether the list of bubbles changed or not (i.e. was a removal made). 141 */ 142 @Synchronized 143 fun sanitizeBubbles(activeUsers: List<Int>): Boolean { 144 for (i in 0 until entitiesByUser.size()) { 145 // First check if the user is a parent / top-level user 146 val parentUserId = entitiesByUser.keyAt(i) 147 if (!activeUsers.contains(parentUserId)) { 148 entitiesByUser.remove(parentUserId) 149 return true 150 } else if (entitiesByUser.get(parentUserId) != null) { 151 // Then check if each of the bubbles in the top-level user, still has a valid user 152 // as it could belong to a profile and have a different id from the parent. 153 return entitiesByUser.get(parentUserId).removeIf { b: BubbleEntity -> 154 !activeUsers.contains(b.userId) 155 } 156 } 157 } 158 return false 159 } 160 161 private fun cache(bubbles: List<BubbleEntity>) { 162 bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> 163 launcherApps.cacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, 164 UserHandle.of(key.userId), LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS) 165 } 166 } 167 168 private fun uncache(bubbles: List<BubbleEntity>) { 169 bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> 170 launcherApps.uncacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, 171 UserHandle.of(key.userId), LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS) 172 } 173 } 174 } 175