• 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 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.Mockito.any
61 import org.mockito.Mockito.anyInt
62 import org.mockito.Mockito.doReturn
63 import org.mockito.Mockito.intThat
64 import org.mockito.Mockito.same
65 import org.testng.Assert.assertThrows
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(pkgName, null, File("/test"), 0, 0,
303                 UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c"))
304         .apply {
305             if (params.isSystem) {
306                 this.flags = this.flags or ApplicationInfo.FLAG_SYSTEM
307             }
308             this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
309             setPkg(pkg)
310         }
311 
312     private fun makeTestData() {
313         mockPkg = makePkg(params.pkgName)
314         mockPkgSetting = makePkgSetting(params.pkgName, mockPkg)
315 
316         if (params.result is Result.NotChanged) {
317             // If verifying no-op behavior, set the current setting to the test values
318             mockPkgSetting.overrideNonLocalizedLabelAndIcon(params.componentName!!, TEST_LABEL,
319                     TEST_ICON, userId)
320         }
321     }
322 
323     private fun mockService(): PackageManagerService {
324         val mockedPkgs = mapOf(
325                 // Must use the test app's UID so that PMS can match them when querying, since
326                 // the static Binder.getCallingUid can't mocked as it's marked final
327                 VALID_PKG to makePkg(VALID_PKG) { uid = Binder.getCallingUid() },
328                 SHARED_PKG to makePkg(SHARED_PKG) { uid = Binder.getCallingUid() },
329                 INVALID_PKG to makePkg(INVALID_PKG) { uid = Binder.getCallingUid() + 1 }
330         )
331         val mockedPkgSettings = mutableMapOf(
332                 VALID_PKG to makePkgSetting(VALID_PKG, mockedPkgs[VALID_PKG]!!),
333                 SHARED_PKG to makePkgSetting(SHARED_PKG, mockedPkgs[SHARED_PKG]!!),
334                 INVALID_PKG to makePkgSetting(INVALID_PKG, mockedPkgs[INVALID_PKG]!!)
335         )
336 
337         var mockActivity: ParsedActivity? = null
338         if (mockedPkgSettings.containsKey(params.pkgName)) {
339             // Add pkgSetting under test so its attributes override the defaults added above
340             mockedPkgSettings.put(params.pkgName, mockPkgSetting)
341 
342             mockActivity = mock<ParsedActivity> {
343                 whenever(this.packageName) { params.pkgName }
344                 whenever(this.nonLocalizedLabel) { DEFAULT_LABEL }
345                 whenever(this.icon) { DEFAULT_ICON }
346                 whenever(this.componentName) { params.componentName }
347                 whenever(this.name) { params.componentName?.className }
348                 whenever(this.isEnabled) { true }
349                 whenever(this.isDirectBootAware) { params.isSystem }
350             }
351         }
352 
353         val mockSettings = Settings(mockedPkgSettings)
354         val mockComponentResolver: ComponentResolver = mockThrowOnUnmocked {
355             params.componentName?.let {
356                 doReturn(mockActivity != null).`when`(this).componentExists(same(it))
357                 doReturn(mockActivity).`when`(this).getActivity(same(it))
358             }
359             whenever(this.snapshot()) { this@mockThrowOnUnmocked }
360             whenever(registerObserver(any())).thenCallRealMethod()
361         }
362         val mockUserManagerService: UserManagerService = mockThrowOnUnmocked {
363             val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent }
364             whenever(this.exists(intThat(matcher))) { true }
365         }
366         val mockUserManagerInternal: UserManagerInternal = mockThrowOnUnmocked {
367             val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent }
368             whenever(this.isUserUnlockingOrUnlocked(intThat(matcher))) { true }
369         }
370         val mockActivityTaskManager: ActivityTaskManagerInternal = mockThrowOnUnmocked {
371             whenever(this.isCallerRecents(anyInt())) { false }
372         }
373         val mockAppsFilter: AppsFilterImpl = mockThrowOnUnmocked {
374             whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(),
375                     any<PackageSetting>(), any<PackageSetting>(), anyInt())) { false }
376             whenever(this.snapshot()) { this@mockThrowOnUnmocked }
377             whenever(registerObserver(any())).thenCallRealMethod()
378         }
379         val mockContext: Context = mockThrowOnUnmocked {
380             whenever(this.getString(
381                     com.android.internal.R.string.config_overrideComponentUiPackage)) { VALID_PKG }
382             whenever(this.checkCallingOrSelfPermission(
383                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)) {
384                 PackageManager.PERMISSION_GRANTED
385             }
386         }
387         val mockSharedLibrariesImpl: SharedLibrariesImpl = mock {
388             whenever(this.snapshot()) { this@mock }
389         }
390         val mockInjector: PackageManagerServiceInjector = mock {
391             whenever(this.lock) { PackageManagerTracedLock() }
392             whenever(this.componentResolver) { mockComponentResolver }
393             whenever(this.userManagerService) { mockUserManagerService }
394             whenever(this.userManagerInternal) { mockUserManagerInternal }
395             whenever(this.settings) { mockSettings }
396             whenever(this.getLocalService(ActivityTaskManagerInternal::class.java)) {
397                 mockActivityTaskManager
398             }
399             whenever(this.appsFilter) { mockAppsFilter }
400             whenever(this.context) { mockContext }
401             whenever(this.handler) { testHandler }
402             whenever(this.sharedLibrariesImpl) { mockSharedLibrariesImpl }
403         }
404         val testParams = PackageManagerServiceTestParams().apply {
405             this.pendingPackageBroadcasts = mockPendingBroadcasts
406             this.resolveComponentName = ComponentName("android", ".Test")
407             this.packages = ArrayMap<String, AndroidPackage>().apply { putAll(mockedPkgs) }
408             this.instantAppRegistry = mock()
409         }
410 
411         return PackageManagerService(mockInjector, testParams)
412     }
413 
414     // If service isn't initialized, then test setup failed and @Afters should be skipped
415     private fun assertServiceInitialized() = Unit.takeIf { ::service.isInitialized }
416 }
417