1 /*
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  */
16 
17 package com.android.wm.shell.bubbles.storage
18 
19 import android.app.ActivityTaskManager.INVALID_TASK_ID
20 import android.content.pm.LauncherApps
21 import android.os.UserHandle
22 import android.testing.AndroidTestingRunner
23 import androidx.test.filters.SmallTest
24 import org.junit.Test
25 import com.android.wm.shell.ShellTestCase
26 import com.google.common.truth.Truth.assertThat
27 import junit.framework.Assert.assertEquals
28 import org.junit.Before
29 import org.junit.runner.RunWith
30 import org.mockito.ArgumentMatchers.any
31 import org.mockito.ArgumentMatchers.anyInt
32 import org.mockito.ArgumentMatchers.anyString
33 import org.mockito.ArgumentMatchers.eq
34 import org.mockito.Mockito
35 import org.mockito.Mockito.mock
36 import org.mockito.Mockito.never
37 import org.mockito.Mockito.reset
38 import org.mockito.Mockito.verify
39 
40 @SmallTest
41 @RunWith(AndroidTestingRunner::class)
42 class BubbleVolatileRepositoryTest : ShellTestCase() {
43 
44     private val user0 = UserHandle.of(0)
45     private val user10_managed = UserHandle.of(10) // In test, acts as workprofile of user0
46     private val user11 = UserHandle.of(11)
47 
48     // user, package, shortcut, notification key, height, res-height, title, taskId, locusId
49     private val bubble1 = BubbleEntity(user0.identifier,
50             "com.example.messenger", "shortcut-1", "0key-1", 120, 0, null, 1, null)
51     private val bubble2 = BubbleEntity(user10_managed.identifier,
52             "com.example.chat", "alice and bob", "10key-2", 0, 16537428, "title", 2, null)
53     private val bubble3 = BubbleEntity(user0.identifier,
54             "com.example.messenger", "shortcut-2", "0key-3", 120, 0, null, INVALID_TASK_ID, null)
55 
56     private val bubble11 = BubbleEntity(user11.identifier,
57             "com.example.messenger", "shortcut-1", "01key-1", 120, 0, null, 3)
58     private val bubble12 = BubbleEntity(user11.identifier,
59             "com.example.chat", "alice and bob", "11key-2", 0, 16537428, "title", INVALID_TASK_ID)
60 
61     private val user0bubbles = listOf(bubble1, bubble2, bubble3)
62     private val user11bubbles = listOf(bubble11, bubble12)
63 
64     private lateinit var repository: BubbleVolatileRepository
65     private lateinit var launcherApps: LauncherApps
66 
67     @Before
68     fun setup() {
69         launcherApps = mock(LauncherApps::class.java)
70         repository = BubbleVolatileRepository(launcherApps)
71     }
72 
73     @Test
74     fun testAddBubbles() {
75         repository.addBubbles(user0.identifier, user0bubbles)
76         repository.addBubbles(user11.identifier, user11bubbles)
77 
78         assertEquals(user0bubbles, repository.getEntities(user0.identifier).toList())
79         assertEquals(user11bubbles, repository.getEntities(user11.identifier).toList())
80 
81         verify(launcherApps).cacheShortcuts(eq(PKG_MESSENGER),
82                 eq(listOf("shortcut-1", "shortcut-2")), eq(user0),
83                 eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS))
84         verify(launcherApps).cacheShortcuts(eq(PKG_CHAT),
85                 eq(listOf("alice and bob")), eq(user10_managed),
86                 eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS))
87 
88         verify(launcherApps).cacheShortcuts(eq(PKG_MESSENGER),
89                 eq(listOf("shortcut-1")), eq(user11),
90                 eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS))
91         verify(launcherApps).cacheShortcuts(eq(PKG_CHAT),
92                 eq(listOf("alice and bob")), eq(user11),
93                 eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS))
94 
95         repository.addBubbles(user0.identifier, listOf(bubble1))
96         assertEquals(listOf(bubble2, bubble3, bubble1), repository.getEntities(user0.identifier))
97 
98         repository.addBubbles(user11.identifier, listOf(bubble12))
99         assertEquals(listOf(bubble11, bubble12), repository.getEntities(user11.identifier))
100 
101         Mockito.verifyNoMoreInteractions(launcherApps)
102     }
103 
104     @Test
105     fun testRemoveBubbles() {
106         repository.addBubbles(user0.identifier, user0bubbles)
107         repository.addBubbles(user11.identifier, user11bubbles)
108 
109         repository.removeBubbles(user0.identifier, listOf(bubble3))
110         assertEquals(listOf(bubble1, bubble2), repository.getEntities(user0.identifier).toList())
111         verify(launcherApps).uncacheShortcuts(eq(PKG_MESSENGER),
112                 eq(listOf("shortcut-2")), eq(user0),
113                 eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS))
114 
115         reset(launcherApps)
116 
117         repository.removeBubbles(user11.identifier, listOf(bubble12))
118         assertEquals(listOf(bubble11), repository.getEntities(user11.identifier).toList())
119         verify(launcherApps).uncacheShortcuts(eq(PKG_CHAT),
120                 eq(listOf("alice and bob")), eq(user11),
121                 eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS))
122     }
123 
124     @Test
125     fun testAddAndRemoveBubblesWhenExceedingCapacity() {
126         repository.capacity = 2
127         // push bubbles beyond capacity
128         repository.addBubbles(user0.identifier, user0bubbles)
129         // verify it is trim down to capacity
130         assertEquals(listOf(bubble2, bubble3), repository.getEntities(user0.identifier).toList())
131         verify(launcherApps).cacheShortcuts(eq(PKG_MESSENGER),
132                 eq(listOf("shortcut-2")), eq(user0),
133                 eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS))
134         verify(launcherApps).cacheShortcuts(eq(PKG_CHAT),
135                 eq(listOf("alice and bob")), eq(user10_managed),
136                 eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS))
137 
138         repository.addBubbles(user0.identifier, listOf(bubble1))
139         // verify the oldest bubble is popped 2, 3
140         assertEquals(listOf(bubble3, bubble1), repository.getEntities(user0.identifier).toList())
141         verify(launcherApps).uncacheShortcuts(eq(PKG_CHAT),
142                 eq(listOf("alice and bob")), eq(user10_managed),
143                 eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS))
144     }
145 
146     @Test
147     fun testAddBubbleMatchesByKey() {
148         val bubble = BubbleEntity(0, "com.example.pkg", "shortcut-id", "key", 120, 0, "title",
149                 1, null)
150         repository.addBubbles(user0.identifier, listOf(bubble))
151         assertEquals(bubble, repository.getEntities(user0.identifier).get(0))
152 
153         // Same key as first bubble but different entry
154         val bubbleModified = BubbleEntity(0, "com.example.pkg", "shortcut-id", "key", 120, 0,
155                 "different title", 2)
156         repository.addBubbles(user0.identifier, listOf(bubbleModified))
157         assertEquals(bubbleModified, repository.getEntities(user0.identifier).get(0))
158     }
159 
160     @Test
161     fun testRemoveBubblesForUser() {
162         repository.addBubbles(user0.identifier, user0bubbles)
163         assertThat(repository.getEntities(user0.identifier).toList())
164                 .isEqualTo(listOf(bubble1, bubble2, bubble3))
165 
166         val ret = repository.removeBubblesForUser(user0.identifier, -1)
167         assertThat(ret).isTrue() // bubbles were removed
168 
169         assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
170         verify(launcherApps, never()).uncacheShortcuts(anyString(),
171                 any(),
172                 any(UserHandle::class.java), anyInt())
173     }
174 
175     @Test
176     fun testRemoveBubblesForUser_parentUserRemoved() {
177         repository.addBubbles(user0.identifier, user0bubbles)
178         // bubble2 is the work profile bubble
179         assertThat(repository.getEntities(user0.identifier).toList())
180                 .isEqualTo(listOf(bubble1, bubble2, bubble3))
181 
182         val ret = repository.removeBubblesForUser(user10_managed.identifier, user0.identifier)
183         assertThat(ret).isTrue() // bubbles were removed
184 
185         assertThat(repository.getEntities(user0.identifier).toList())
186                 .isEqualTo(listOf(bubble1, bubble3))
187         verify(launcherApps, never()).uncacheShortcuts(anyString(),
188                 any(),
189                 any(UserHandle::class.java), anyInt())
190     }
191 
192     @Test
193     fun testRemoveBubblesForUser_withoutBubbles() {
194         repository.addBubbles(user0.identifier, user0bubbles)
195         assertThat(repository.getEntities(user0.identifier).toList())
196                 .isEqualTo(listOf(bubble1, bubble2, bubble3))
197 
198         val ret = repository.removeBubblesForUser(user11.identifier, -1)
199         assertThat(ret).isFalse() // bubbles were NOT removed
200 
201         assertThat(repository.getEntities(user0.identifier).toList())
202                 .isEqualTo(listOf(bubble1, bubble2, bubble3))
203         verify(launcherApps, never()).uncacheShortcuts(anyString(),
204                 any(),
205                 any(UserHandle::class.java), anyInt())
206     }
207 
208     @Test
209     fun testSanitizeBubbles_noChanges() {
210         repository.addBubbles(user0.identifier, user0bubbles)
211         assertThat(repository.getEntities(user0.identifier).toList())
212                 .isEqualTo(listOf(bubble1, bubble2, bubble3))
213         repository.addBubbles(user11.identifier, user11bubbles)
214         assertThat(repository.getEntities(user11.identifier).toList())
215                 .isEqualTo(listOf(bubble11, bubble12))
216 
217         val ret = repository.sanitizeBubbles(listOf(user0.identifier,
218                 user10_managed.identifier,
219                 user11.identifier))
220         assertThat(ret).isFalse() // bubbles were NOT removed
221 
222         verify(launcherApps, never()).uncacheShortcuts(anyString(),
223                 any(),
224                 any(UserHandle::class.java), anyInt())
225     }
226 
227     @Test
228     fun testSanitizeBubbles_userRemoved() {
229         repository.addBubbles(user0.identifier, user0bubbles)
230         assertThat(repository.getEntities(user0.identifier).toList())
231                 .isEqualTo(listOf(bubble1, bubble2, bubble3))
232         repository.addBubbles(user11.identifier, user11bubbles)
233         assertThat(repository.getEntities(user11.identifier).toList())
234                 .isEqualTo(listOf(bubble11, bubble12))
235 
236         val ret = repository.sanitizeBubbles(listOf(user11.identifier))
237         assertThat(ret).isTrue() // bubbles were removed
238 
239         assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
240         verify(launcherApps, never()).uncacheShortcuts(anyString(),
241                 any(),
242                 any(UserHandle::class.java), anyInt())
243 
244         // User 11 bubbles should still be here
245         assertThat(repository.getEntities(user11.identifier).toList())
246                 .isEqualTo(listOf(bubble11, bubble12))
247     }
248 
249     @Test
250     fun testSanitizeBubbles_userParentRemoved() {
251         repository.addBubbles(user0.identifier, user0bubbles)
252         assertThat(repository.getEntities(user0.identifier).toList())
253                 .isEqualTo(listOf(bubble1, bubble2, bubble3))
254 
255         repository.addBubbles(user11.identifier, user11bubbles)
256         assertThat(repository.getEntities(user11.identifier).toList())
257                 .isEqualTo(listOf(bubble11, bubble12))
258 
259         val ret = repository.sanitizeBubbles(listOf(user0.identifier, user11.identifier))
260         assertThat(ret).isTrue() // bubbles were removed
261         // bubble2 is the work profile bubble and should be removed
262         assertThat(repository.getEntities(user0.identifier).toList())
263                 .isEqualTo(listOf(bubble1, bubble3))
264         verify(launcherApps, never()).uncacheShortcuts(anyString(),
265                 any(),
266                 any(UserHandle::class.java), anyInt())
267 
268         // User 11 bubbles should still be here
269         assertThat(repository.getEntities(user11.identifier).toList())
270                 .isEqualTo(listOf(bubble11, bubble12))
271     }
272 
273     @Test
274     fun testRemoveBubbleForUser_invalidInputDoesntCrash() {
275         repository.removeBubblesForUser(-1, 0)
276         repository.removeBubblesForUser(-1, -1)
277     }
278 }
279 
280 private const val PKG_MESSENGER = "com.example.messenger"
281 private const val PKG_CHAT = "com.example.chat"
282