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