1 /*
<lambda>null2  * Copyright (C) 2024 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.common.usagestats.data.repository
18 
19 import android.app.usage.UsageEvents
20 import android.app.usage.UsageEventsQuery
21 import android.app.usage.UsageStatsManager
22 import android.os.UserHandle
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.SmallTest
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.common.usagestats.data.model.UsageStatsQuery
27 import com.android.systemui.common.usagestats.shared.model.ActivityEventModel
28 import com.android.systemui.common.usagestats.shared.model.ActivityEventModel.Lifecycle
29 import com.android.systemui.kosmos.backgroundCoroutineContext
30 import com.android.systemui.kosmos.testScope
31 import com.android.systemui.testKosmos
32 import com.google.common.truth.Truth.assertThat
33 import kotlinx.coroutines.test.runTest
34 import org.junit.Test
35 import org.junit.runner.RunWith
36 import org.mockito.kotlin.any
37 import org.mockito.kotlin.doAnswer
38 import org.mockito.kotlin.mock
39 
40 @SmallTest
41 @RunWith(AndroidJUnit4::class)
42 class UsageStatsRepositoryTest : SysuiTestCase() {
43 
44     private val kosmos = testKosmos()
45     private val testScope = kosmos.testScope
46 
47     private val fakeUsageStatsManager = FakeUsageStatsManager()
48 
49     private val usageStatsManager =
50         mock<UsageStatsManager> {
51             on { queryEvents(any()) } doAnswer
52                 { inv ->
53                     val query = inv.getArgument(0) as UsageEventsQuery
54                     fakeUsageStatsManager.queryEvents(query)
55                 }
56         }
57 
58     private val underTest by lazy {
59         UsageStatsRepositoryImpl(
60             bgContext = kosmos.backgroundCoroutineContext,
61             usageStatsManager = usageStatsManager,
62         )
63     }
64 
65     @Test
66     fun testQueryWithBeginAndEndTime() =
67         testScope.runTest {
68             with(fakeUsageStatsManager) {
69                 // This event is outside the queried time, and therefore should
70                 // not be returned.
71                 addEvent(
72                     type = UsageEvents.Event.ACTIVITY_RESUMED,
73                     timestamp = 5,
74                     instanceId = 1,
75                 )
76                 addEvent(
77                     type = UsageEvents.Event.ACTIVITY_PAUSED,
78                     timestamp = 10,
79                     instanceId = 1,
80                 )
81                 addEvent(
82                     type = UsageEvents.Event.ACTIVITY_STOPPED,
83                     timestamp = 20,
84                     instanceId = 2,
85                 )
86                 // This event is outside the queried time, and therefore should
87                 // not be returned.
88                 addEvent(
89                     type = UsageEvents.Event.ACTIVITY_DESTROYED,
90                     timestamp = 50,
91                     instanceId = 2,
92                 )
93             }
94 
95             assertThat(
96                     underTest.queryActivityEvents(
97                         UsageStatsQuery(MAIN_USER, startTime = 10, endTime = 50),
98                     ),
99                 )
100                 .containsExactly(
101                     ActivityEventModel(
102                         instanceId = 1,
103                         packageName = DEFAULT_PACKAGE,
104                         lifecycle = Lifecycle.PAUSED,
105                         timestamp = 10,
106                     ),
107                     ActivityEventModel(
108                         instanceId = 2,
109                         packageName = DEFAULT_PACKAGE,
110                         lifecycle = Lifecycle.STOPPED,
111                         timestamp = 20,
112                     ),
113                 )
114         }
115 
116     @Test
117     fun testQueryForDifferentUsers() =
118         testScope.runTest {
119             with(fakeUsageStatsManager) {
120                 addEvent(
121                     user = MAIN_USER,
122                     type = UsageEvents.Event.ACTIVITY_PAUSED,
123                     timestamp = 10,
124                     instanceId = 1,
125                 )
126                 addEvent(
127                     user = SECONDARY_USER,
128                     type = UsageEvents.Event.ACTIVITY_RESUMED,
129                     timestamp = 11,
130                     instanceId = 2,
131                 )
132             }
133 
134             assertThat(
135                     underTest.queryActivityEvents(
136                         UsageStatsQuery(MAIN_USER, startTime = 10, endTime = 15),
137                     ),
138                 )
139                 .containsExactly(
140                     ActivityEventModel(
141                         instanceId = 1,
142                         packageName = DEFAULT_PACKAGE,
143                         lifecycle = Lifecycle.PAUSED,
144                         timestamp = 10,
145                     ),
146                 )
147         }
148 
149     @Test
150     fun testQueryForSpecificPackages() =
151         testScope.runTest {
152             with(fakeUsageStatsManager) {
153                 addEvent(
154                     packageName = DEFAULT_PACKAGE,
155                     type = UsageEvents.Event.ACTIVITY_PAUSED,
156                     timestamp = 10,
157                     instanceId = 1,
158                 )
159                 addEvent(
160                     packageName = OTHER_PACKAGE,
161                     type = UsageEvents.Event.ACTIVITY_RESUMED,
162                     timestamp = 11,
163                     instanceId = 2,
164                 )
165             }
166 
167             assertThat(
168                     underTest.queryActivityEvents(
169                         UsageStatsQuery(
170                             MAIN_USER,
171                             startTime = 10,
172                             endTime = 10000,
173                             packageNames = listOf(OTHER_PACKAGE),
174                         ),
175                     ),
176                 )
177                 .containsExactly(
178                     ActivityEventModel(
179                         instanceId = 2,
180                         packageName = OTHER_PACKAGE,
181                         lifecycle = Lifecycle.RESUMED,
182                         timestamp = 11,
183                     ),
184                 )
185         }
186 
187     @Test
188     fun testNonActivityEvent() =
189         testScope.runTest {
190             with(fakeUsageStatsManager) {
191                 addEvent(
192                     type = UsageEvents.Event.CHOOSER_ACTION,
193                     timestamp = 10,
194                     instanceId = 1,
195                 )
196             }
197 
198             assertThat(
199                     underTest.queryActivityEvents(
200                         UsageStatsQuery(
201                             MAIN_USER,
202                             startTime = 1,
203                             endTime = 20,
204                         ),
205                     ),
206                 )
207                 .isEmpty()
208         }
209 
210     private class FakeUsageStatsManager() {
211         private val events = mutableMapOf<Int, MutableList<UsageEvents.Event>>()
212 
213         fun queryEvents(query: UsageEventsQuery): UsageEvents {
214             val results =
215                 events
216                     .getOrDefault(query.userId, emptyList())
217                     .filter { event ->
218                         query.packageNames.isEmpty() ||
219                             query.packageNames.contains(event.packageName)
220                     }
221                     .filter { event ->
222                         event.timeStamp in query.beginTimeMillis until query.endTimeMillis
223                     }
224                     .filter { event ->
225                         query.eventTypes.isEmpty() || query.eventTypes.contains(event.eventType)
226                     }
227             return UsageEvents(results, emptyArray())
228         }
229 
230         fun addEvent(
231             type: Int,
232             instanceId: Int = 0,
233             user: UserHandle = MAIN_USER,
234             packageName: String = DEFAULT_PACKAGE,
235             timestamp: Long,
236         ) {
237             events
238                 .getOrPut(user.identifier) { mutableListOf() }
239                 .add(
240                     UsageEvents.Event(type, timestamp).apply {
241                         mPackage = packageName
242                         mInstanceId = instanceId
243                     }
244                 )
245         }
246     }
247 
248     private companion object {
249         const val DEFAULT_PACKAGE = "pkg.default"
250         const val OTHER_PACKAGE = "pkg.other"
251         val MAIN_USER: UserHandle = UserHandle.of(0)
252         val SECONDARY_USER: UserHandle = UserHandle.of(1)
253     }
254 }
255