• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server.pm.test.override
18 
19 import android.app.PropertyInvalidatedCache
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.pm.PackageManager
23 import android.content.pm.parsing.component.ParsedActivity
24 import android.os.Binder
25 import android.os.UserHandle
26 import android.util.ArrayMap
27 import com.android.server.pm.AppsFilter
28 import com.android.server.pm.ComponentResolver
29 import com.android.server.pm.PackageManagerService
30 import com.android.server.pm.PackageManagerTracedLock
31 import com.android.server.pm.PackageSetting
32 import com.android.server.pm.Settings
33 import com.android.server.pm.UserManagerInternal
34 import com.android.server.pm.UserManagerService
35 import com.android.server.pm.parsing.pkg.AndroidPackage
36 import com.android.server.pm.parsing.pkg.PackageImpl
37 import com.android.server.pm.parsing.pkg.ParsedPackage
38 import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType
39 import com.android.server.testutils.TestHandler
40 import com.android.server.testutils.mock
41 import com.android.server.testutils.mockThrowOnUnmocked
42 import com.android.server.testutils.spy
43 import com.android.server.testutils.whenever
44 import com.android.server.wm.ActivityTaskManagerInternal
45 import com.google.common.truth.Truth.assertThat
46 import org.junit.After
47 import org.junit.Before
48 import org.junit.BeforeClass
49 import org.junit.Test
50 import org.junit.runner.RunWith
51 import org.junit.runners.Parameterized
52 import org.mockito.Mockito.any
53 import org.mockito.Mockito.anyInt
54 import org.mockito.Mockito.clearInvocations
55 import org.mockito.Mockito.intThat
56 import org.mockito.Mockito.never
57 import org.mockito.Mockito.same
58 import org.mockito.Mockito.verify
59 import org.testng.Assert.assertThrows
60 import java.io.File
61 import java.util.UUID
62 
63 @RunWith(Parameterized::class)
64 class PackageManagerComponentLabelIconOverrideTest {
65 
66     companion object {
67         private const val VALID_PKG = "com.android.server.pm.test.override"
68         private const val SHARED_PKG = "com.android.server.pm.test.override.shared"
69         private const val INVALID_PKG = "com.android.server.pm.test.override.invalid"
70         private const val NON_EXISTENT_PKG = "com.android.server.pm.test.override.nonexistent"
71 
72         private const val SEND_PENDING_BROADCAST = 1 // PackageManagerService.SEND_PENDING_BROADCAST
73 
74         private const val DEFAULT_LABEL = "DefaultLabel"
75         private const val TEST_LABEL = "TestLabel"
76 
77         private const val DEFAULT_ICON = R.drawable.black16x16
78         private const val TEST_ICON = R.drawable.white16x16
79 
80         private const val COMPONENT_CLASS_NAME = ".TestComponent"
81 
82         sealed class Result {
83             // Component label/icon changed, message sent to send broadcast
84             object Changed : Result()
85 
86             // Component label/icon changed, message was pending, not re-sent
87             object ChangedWithoutNotify : Result()
88 
89             // Component label/icon did not changed, was already equivalent
90             object NotChanged : Result()
91 
92             // Updating label/icon encountered a specific exception
93             data class Exception(val type: Class<out java.lang.Exception>) : Result()
94         }
95 
96         @Parameterized.Parameters(name = "{0}")
97         @JvmStatic
98         fun parameters() = arrayOf(
99                 // Start with an array of the simplest known inputs and expected outputs
100                 Params(VALID_PKG, AppType.SYSTEM_APP, Result.Changed),
101                 Params(SHARED_PKG, AppType.SYSTEM_APP, Result.Changed),
102                 Params(INVALID_PKG, AppType.SYSTEM_APP, SecurityException::class.java),
103                 Params(NON_EXISTENT_PKG, AppType.SYSTEM_APP, SecurityException::class.java)
104         )
105                 .flatMap { param ->
106                     mutableListOf(param).apply {
107                         if (param.result is Result.Changed) {
108                             // For each param that would've succeeded, also verify that if a change
109                             // happened, but a message was pending, another is not re-queued/reset
110                             this += param.copy(result = Result.ChangedWithoutNotify)
111                             // Also verify that when the component is already configured, no change
112                             // is propagated
113                             this += param.copy(result = Result.NotChanged)
114                         }
115                         // For all params, verify that an invalid component will cause an
116                         // IllegalArgumentException, instead of result initially specified
117                         this += param.copy(componentName = null,
118                                 result = Result.Exception(IllegalArgumentException::class.java))
119                         // Also verify an updated system app variant, which should have the same
120                         // result as a vanilla system app
121                         this += param.copy(appType = AppType.UPDATED_SYSTEM_APP)
122                         // Also verify a non-system app will cause a failure, since normal apps
123                         // are not allowed to edit their label/icon
124                         this += param.copy(appType = AppType.NORMAL_APP,
125                                 result = Result.Exception(SecurityException::class.java))
126                     }
127                 }
128 
129         @BeforeClass
130         @JvmStatic
131         fun disablePropertyInvalidatedCache() {
132             // Disable binder caches in this process.
133             PropertyInvalidatedCache.disableForTestMode()
134         }
135 
136         data class Params(
137             val pkgName: String,
138             private val appType: AppType,
139             val result: Result,
140             val componentName: ComponentName? = ComponentName(pkgName, COMPONENT_CLASS_NAME)
141         ) {
142             constructor(pkgName: String, appType: AppType, exception: Class<out Exception>)
143                     : this(pkgName, appType, Result.Exception(exception))
144 
145             val expectedLabel = when (result) {
146                 Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_LABEL
147                 is Result.Exception -> DEFAULT_LABEL
148             }
149 
150             val expectedIcon = when (result) {
151                 Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_ICON
152                 is Result.Exception -> DEFAULT_ICON
153             }
154 
155             val isUpdatedSystemApp = appType == AppType.UPDATED_SYSTEM_APP
156             val isSystem = appType == AppType.SYSTEM_APP || isUpdatedSystemApp
157 
158             override fun toString(): String {
159                 val resultString = when (result) {
160                     Result.Changed -> "Changed"
161                     Result.ChangedWithoutNotify -> "ChangedWithoutNotify"
162                     Result.NotChanged -> "NotChanged"
163                     is Result.Exception -> result.type.simpleName
164                 }
165 
166                 // Nicer formatting for the test method suffix
167                 return "pkg=$pkgName, type=$appType, component=$componentName, result=$resultString"
168             }
169 
170             enum class AppType { SYSTEM_APP, UPDATED_SYSTEM_APP, NORMAL_APP }
171         }
172     }
173 
174     @Parameterized.Parameter(0)
175     lateinit var params: Params
176 
177     private lateinit var testHandler: TestHandler
178     private lateinit var mockPendingBroadcasts: PackageManagerService.PendingPackageBroadcasts
179     private lateinit var mockPkg: AndroidPackage
180     private lateinit var mockPkgSetting: PackageSetting
181     private lateinit var service: PackageManagerService
182 
183     private val userId = UserHandle.getCallingUserId()
184     private val userIdDifferent = userId + 1
185 
186     @Before
187     fun setUpMocks() {
188         makeTestData()
189 
190         testHandler = TestHandler(null)
191         if (params.result is Result.ChangedWithoutNotify) {
192             // Case where the handler already has a message and so another should not be sent.
193             // This case will verify that only 1 message exists, which is the one added here.
194             testHandler.sendEmptyMessage(SEND_PENDING_BROADCAST)
195         }
196 
197         mockPendingBroadcasts = PackageManagerService.PendingPackageBroadcasts()
198 
199         service = mockService()
200     }
201 
202     @Test
203     fun updateComponentLabelIcon() {
204         fun runUpdate() {
205             service.updateComponentLabelIcon(params.componentName, TEST_LABEL, TEST_ICON, userId)
206         }
207 
208         when (val result = params.result) {
209             Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> {
210                 runUpdate()
211                 verify(mockPkgSetting).overrideNonLocalizedLabelAndIcon(params.componentName!!,
212                         TEST_LABEL, TEST_ICON, userId)
213             }
214             is Result.Exception -> {
215                 assertThrows(result.type) { runUpdate() }
216                 verify(mockPkgSetting, never()).overrideNonLocalizedLabelAndIcon(
217                         any<ComponentName>(), any(), anyInt(), anyInt())
218             }
219         }
220     }
221 
222     @After
223     fun verifyExpectedResult() {
224         if (params.componentName != null) {
225             val activityInfo = service.getActivityInfo(params.componentName, 0, userId)
226             if (activityInfo != null) {
227                 assertThat(activityInfo.nonLocalizedLabel).isEqualTo(params.expectedLabel)
228                 assertThat(activityInfo.icon).isEqualTo(params.expectedIcon)
229             }
230         }
231     }
232 
233     @After
234     fun verifyDifferentUserUnchanged() {
235         when (params.result) {
236             Result.Changed, Result.ChangedWithoutNotify -> {
237                 val activityInfo = service.getActivityInfo(params.componentName, 0, userIdDifferent)
238                 assertThat(activityInfo.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL)
239                 assertThat(activityInfo.icon).isEqualTo(DEFAULT_ICON)
240             }
241             Result.NotChanged, is Result.Exception -> {}
242         }.run { /*exhaust*/ }
243     }
244 
245     @After
246     fun verifyHandlerHasMessage() {
247         when (params.result) {
248             is Result.Changed, is Result.ChangedWithoutNotify -> {
249                 assertThat(testHandler.pendingMessages).hasSize(1)
250                 assertThat(testHandler.pendingMessages.first().message.what)
251                         .isEqualTo(SEND_PENDING_BROADCAST)
252             }
253             is Result.NotChanged, is Result.Exception -> {
254                 assertThat(testHandler.pendingMessages).hasSize(0)
255             }
256         }.run { /*exhaust*/ }
257     }
258 
259     @After
260     fun verifyPendingBroadcast() {
261         when (params.result) {
262             is Result.Changed, Result.ChangedWithoutNotify -> {
263                 assertThat(mockPendingBroadcasts.get(userId, params.pkgName))
264                         .containsExactly(params.componentName!!.className)
265                         .inOrder()
266             }
267             is Result.NotChanged, is Result.Exception -> {
268                 assertThat(mockPendingBroadcasts.get(userId, params.pkgName)).isNull()
269             }
270         }.run { /*exhaust*/ }
271     }
272 
273     private fun makePkg(pkgName: String, block: ParsedPackage.() -> Unit = {}) =
274             PackageImpl.forTesting(pkgName)
275                     .setEnabled(true)
276                     .let { it.hideAsParsed() as ParsedPackage }
277                     .setSystem(params.isSystem)
278                     .apply(block)
279                     .hideAsFinal()
280 
281     private fun makePkgSetting(pkgName: String) = spy(
282         PackageSetting(
283             pkgName, null, File("/test"),
284             null, null, null, null, 0, 0, 0, 0, null, null, null,
285             UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
286         )
287     ) {
288         this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
289     }
290 
291     private fun makeTestData() {
292         mockPkg = makePkg(params.pkgName)
293         mockPkgSetting = makePkgSetting(params.pkgName)
294 
295         if (params.result is Result.NotChanged) {
296             // If verifying no-op behavior, set the current setting to the test values
297             mockPkgSetting.overrideNonLocalizedLabelAndIcon(params.componentName!!, TEST_LABEL,
298                     TEST_ICON, userId)
299             // Then clear the mock because the line above just incremented it
300             clearInvocations(mockPkgSetting)
301         }
302     }
303 
304     private fun mockService(): PackageManagerService {
305         val mockedPkgs = mapOf(
306                 // Must use the test app's UID so that PMS can match them when querying, since
307                 // the static Binder.getCallingUid can't mocked as it's marked final
308                 VALID_PKG to makePkg(VALID_PKG) { uid = Binder.getCallingUid() },
309                 SHARED_PKG to makePkg(SHARED_PKG) { uid = Binder.getCallingUid() },
310                 INVALID_PKG to makePkg(INVALID_PKG) { uid = Binder.getCallingUid() + 1 }
311         )
312         val mockedPkgSettings = mutableMapOf(
313                 VALID_PKG to makePkgSetting(VALID_PKG),
314                 SHARED_PKG to makePkgSetting(SHARED_PKG),
315                 INVALID_PKG to makePkgSetting(INVALID_PKG)
316         )
317 
318         var mockActivity: ParsedActivity? = null
319         if (mockedPkgSettings.containsKey(params.pkgName)) {
320             // Add pkgSetting under test so its attributes override the defaults added above
321             mockedPkgSettings.put(params.pkgName, mockPkgSetting)
322 
323             mockActivity = mock<ParsedActivity> {
324                 whenever(this.packageName) { params.pkgName }
325                 whenever(this.nonLocalizedLabel) { DEFAULT_LABEL }
326                 whenever(this.icon) { DEFAULT_ICON }
327                 whenever(this.componentName) { params.componentName }
328                 whenever(this.name) { params.componentName?.className }
329                 whenever(this.isEnabled) { true }
330                 whenever(this.isDirectBootAware) { params.isSystem }
331             }
332         }
333 
334         val mockSettings = Settings(mockedPkgSettings)
335         val mockComponentResolver: ComponentResolver = mockThrowOnUnmocked {
336             params.componentName?.let {
337                 whenever(this.componentExists(same(it))) { mockActivity != null }
338                 whenever(this.getActivity(same(it))) { mockActivity }
339             }
340         }
341         val mockUserManagerService: UserManagerService = mockThrowOnUnmocked {
342             val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent }
343             whenever(this.exists(intThat(matcher))) { true }
344         }
345         val mockUserManagerInternal: UserManagerInternal = mockThrowOnUnmocked {
346             val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent }
347             whenever(this.isUserUnlockingOrUnlocked(intThat(matcher))) { true }
348         }
349         val mockActivityTaskManager: ActivityTaskManagerInternal = mockThrowOnUnmocked {
350             whenever(this.isCallerRecents(anyInt())) { false }
351         }
352         val mockAppsFilter: AppsFilter = mockThrowOnUnmocked {
353             whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(),
354                     any<PackageSetting>(), anyInt())) { false }
355         }
356         val mockContext: Context = mockThrowOnUnmocked {
357             whenever(this.getString(
358                     com.android.internal.R.string.config_overrideComponentUiPackage)) { VALID_PKG }
359             whenever(this.checkCallingOrSelfPermission(
360                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)) {
361                 PackageManager.PERMISSION_GRANTED
362             }
363         }
364         val mockInjector: PackageManagerService.Injector = mock {
365             whenever(this.lock) { PackageManagerTracedLock() }
366             whenever(this.componentResolver) { mockComponentResolver }
367             whenever(this.userManagerService) { mockUserManagerService }
368             whenever(this.getUserManagerInternal()) { mockUserManagerInternal }
369             whenever(this.settings) { mockSettings }
370             whenever(this.getLocalService(ActivityTaskManagerInternal::class.java)) {
371                 mockActivityTaskManager
372             }
373             whenever(this.appsFilter) { mockAppsFilter }
374             whenever(this.context) { mockContext }
375             whenever(this.getHandler()) { testHandler }
376         }
377         val testParams = PackageManagerService.TestParams().apply {
378             this.pendingPackageBroadcasts = mockPendingBroadcasts
379             this.resolveComponentName = ComponentName("android", ".Test")
380             this.packages = ArrayMap<String, AndroidPackage>().apply { putAll(mockedPkgs) }
381         }
382 
383         return PackageManagerService(mockInjector, testParams)
384     }
385 }
386