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