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