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