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.content.pm.LauncherApps 19 import android.os.UserHandle 20 import android.util.SparseArray 21 import com.android.internal.annotations.VisibleForTesting 22 import com.android.wm.shell.bubbles.ShortcutKey 23 24 private const val CAPACITY = 16 25 26 /** 27 * BubbleVolatileRepository holds the most updated snapshot of list of bubbles for in-memory 28 * manipulation. 29 */ 30 class BubbleVolatileRepository(private val launcherApps: LauncherApps) { 31 32 /** 33 * Set of bubbles per user. Each set of bubbles is ordered by recency. 34 */ 35 private var entitiesByUser = SparseArray<MutableList<BubbleEntity>>() 36 37 /** 38 * The capacity of the cache. 39 */ 40 @VisibleForTesting 41 var capacity = CAPACITY 42 43 /** 44 * Returns a snapshot of all the bubbles, a map of the userId to bubble list. 45 */ 46 val bubbles: SparseArray<List<BubbleEntity>> 47 @Synchronized 48 get() { 49 val map = SparseArray<List<BubbleEntity>>() 50 for (i in 0 until entitiesByUser.size()) { 51 val k = entitiesByUser.keyAt(i) 52 val v = entitiesByUser.valueAt(i) 53 map.put(k, v.toList()) 54 } 55 return map 56 } 57 58 /** 59 * Returns the entity list of the provided user's bubbles or creates one if it doesn't exist. 60 */ 61 @Synchronized 62 fun getEntities(userId: Int): MutableList<BubbleEntity> { 63 val entities = entitiesByUser.get(userId) 64 return when (entities) { 65 null -> mutableListOf<BubbleEntity>().also { 66 entitiesByUser.put(userId, it) 67 } 68 else -> entities 69 } 70 } 71 72 /** 73 * Add the bubbles to memory and perform a de-duplication. In case a bubble already exists, 74 * it will be moved to the last. 75 */ 76 @Synchronized 77 fun addBubbles(userId: Int, bubbles: List<BubbleEntity>) { 78 if (bubbles.isEmpty()) return 79 // Get the list for this user 80 var entities = getEntities(userId) 81 // Verify the size of given bubbles is within capacity, otherwise trim down to capacity 82 val bubblesInRange = bubbles.takeLast(capacity) 83 // To ensure natural ordering of the bubbles, removes bubbles which already exist 84 val uniqueBubbles = bubblesInRange.filterNot { b: BubbleEntity -> 85 entities.removeIf { e: BubbleEntity -> b.key == e.key } } 86 val overflowCount = entities.size + bubblesInRange.size - capacity 87 if (overflowCount > 0) { 88 // Uncache ShortcutInfo of bubbles that will be removed due to capacity 89 uncache(entities.take(overflowCount)) 90 entities = entities.drop(overflowCount).toMutableList() 91 } 92 entities.addAll(bubblesInRange) 93 entitiesByUser.put(userId, entities) 94 cache(uniqueBubbles) 95 } 96 97 @Synchronized 98 fun removeBubbles(userId: Int, bubbles: List<BubbleEntity>) = 99 uncache(bubbles.filter { b: BubbleEntity -> 100 getEntities(userId).removeIf { e: BubbleEntity -> b.key == e.key } }) 101 102 private fun cache(bubbles: List<BubbleEntity>) { 103 bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> 104 launcherApps.cacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, 105 UserHandle.of(key.userId), LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS) 106 } 107 } 108 109 private fun uncache(bubbles: List<BubbleEntity>) { 110 bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> 111 launcherApps.uncacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, 112 UserHandle.of(key.userId), LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS) 113 } 114 } 115 } 116