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 android.content.pm 18 19 import android.Manifest 20 import android.app.Instrumentation 21 import android.app.PendingIntent 22 import android.content.BroadcastReceiver 23 import android.content.Context 24 import android.content.Intent 25 import android.content.IntentFilter 26 import android.content.pm.PackageInstaller.SessionParams 27 import android.os.Handler 28 import android.os.HandlerThread 29 import android.platform.test.annotations.Presubmit 30 import android.util.Log 31 import androidx.test.InstrumentationRegistry 32 import androidx.test.filters.LargeTest 33 import com.android.compatibility.common.util.ShellIdentityUtils 34 import com.google.common.truth.Truth.assertThat 35 import java.util.concurrent.ArrayBlockingQueue 36 import java.util.concurrent.CountDownLatch 37 import java.util.concurrent.TimeUnit 38 import kotlin.random.Random 39 import org.junit.After 40 import org.testng.Assert.assertThrows 41 import org.junit.Assert.fail 42 import org.junit.Before 43 import org.junit.Test 44 45 /** 46 * For verifying public [PackageInstaller] session APIs. This differs from 47 * [com.android.server.pm.PackageInstallerSessionTest] in services because that mocks the session, 48 * whereas this test uses the installer on device. 49 */ 50 @Presubmit 51 class PackageSessionTests { 52 53 companion object { 54 /** 55 * Permissions marked "hardRestricted" or "softRestricted" in core/res/AndroidManifest.xml. 56 */ 57 private val RESTRICTED_PERMISSIONS = listOf( 58 "android.permission.SEND_SMS", 59 "android.permission.RECEIVE_SMS", 60 "android.permission.READ_SMS", 61 "android.permission.RECEIVE_WAP_PUSH", 62 "android.permission.RECEIVE_MMS", 63 "android.permission.READ_CELL_BROADCASTS", 64 "android.permission.ACCESS_BACKGROUND_LOCATION", 65 "android.permission.READ_CALL_LOG", 66 "android.permission.WRITE_CALL_LOG", 67 "android.permission.PROCESS_OUTGOING_CALLS" 68 ) 69 70 private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app" 71 private const val INTENT_ACTION = "com.android.server.pm.test.test_app.action" 72 } 73 74 private val TAG = "PackageSessionTests" 75 private val context: Context = InstrumentationRegistry.getContext() 76 private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() 77 private val installer = context.packageManager.packageInstaller 78 private var callback: SessionStatusTrackerCallback? = null 79 private var handlerThread: HandlerThread? = null 80 private var handler: Handler? = null 81 82 private val receiver = object : BroadcastReceiver() { 83 private val results = ArrayBlockingQueue<Intent>(1) 84 85 override fun onReceive(context: Context, intent: Intent) { 86 // Added as a safety net. Have observed instances where the Queue isn't empty which 87 // causes the test suite to crash. 88 if (results.size != 0) { 89 clear() 90 } 91 results.add(intent) 92 } 93 94 fun makeIntentSender(sessionId: Int) = PendingIntent.getBroadcast(context, sessionId, 95 Intent(INTENT_ACTION).setPackage(context.packageName), 96 PendingIntent.FLAG_UPDATE_CURRENT 97 or PendingIntent.FLAG_MUTABLE).intentSender 98 99 fun getResult(unit: TimeUnit, timeout: Long) = results.poll(timeout, unit) 100 101 fun clear() = results.clear() 102 } 103 104 class SessionStatusTrackerCallback : PackageInstaller.SessionCallback { 105 private val TAG: String = "SessionStatusTrackerCallback" 106 private val DEFAULT_TIMEOUT: Long = 30 107 private var mSessionActiveLatch: CountDownLatch? = null 108 private var mSessionInactiveLatch: CountDownLatch? = null 109 private var mSessionFinishLatch: CountDownLatch? = null 110 private val mSessionIds = mutableSetOf<Int>() 111 112 constructor(sessionActiveCount: Int = 0, sessionInactiveCount: Int = 0) { 113 this.mSessionActiveLatch = CountDownLatch(sessionActiveCount) 114 this.mSessionInactiveLatch = CountDownLatch(sessionInactiveCount) 115 } 116 117 constructor(sessionFinishCount: Int = 0) { 118 this.mSessionFinishLatch = CountDownLatch(sessionFinishCount) 119 } 120 121 override fun onCreated(sessionId: Int) { 122 mSessionIds.add(sessionId) 123 } 124 125 override fun onBadgingChanged(sessionId: Int) {} 126 127 override fun onActiveChanged(sessionId: Int, active: Boolean) { 128 if (mSessionIds.contains(sessionId)) { 129 if (active) { 130 mSessionActiveLatch?.countDown() 131 } else { 132 mSessionInactiveLatch?.countDown() 133 } 134 } else { 135 Log.d(TAG, "Did not find session ID $sessionId which was opened.") 136 } 137 } 138 139 override fun onProgressChanged(sessionId: Int, progress: Float) {} 140 141 override fun onFinished(sessionId: Int, success: Boolean) { 142 if (!success and mSessionIds.contains(sessionId)) { 143 mSessionFinishLatch?.countDown() 144 } 145 } 146 147 fun awaitSessionActiveCallbacks() { 148 mSessionActiveLatch?.await(DEFAULT_TIMEOUT, TimeUnit.SECONDS) 149 } 150 151 fun awaitSessionInactiveCallbacks() { 152 mSessionInactiveLatch?.await(DEFAULT_TIMEOUT, TimeUnit.SECONDS) 153 } 154 155 fun awaitSessionFinishCallbacks() { 156 mSessionFinishLatch?.await(DEFAULT_TIMEOUT, TimeUnit.SECONDS) 157 } 158 159 fun resetLatches() { 160 mSessionActiveLatch = null 161 mSessionInactiveLatch = null 162 mSessionFinishLatch = null 163 } 164 } 165 166 @Before 167 fun registerReceiver() { 168 receiver.clear() 169 context.registerReceiver(receiver, IntentFilter(INTENT_ACTION)) 170 } 171 172 @After 173 fun unregisterReceiver() { 174 context.unregisterReceiver(receiver) 175 } 176 177 @Before 178 @After 179 fun abandonAllSessions() { 180 instrumentation.uiAutomation 181 .executeShellCommand("pm uninstall $TEST_PKG_NAME") 182 183 installer.mySessions.asSequence() 184 .map { it.sessionId } 185 .forEach { 186 try { 187 installer.abandonSession(it) 188 } catch (ignored: Exception) { 189 Log.e(TAG, "Abandon failed: ", ignored) 190 // Querying for sessions checks by calling package name, but abandoning 191 // checks by UID, which won't match if this test failed to clean up 192 // on a previous install + run + uninstall, so ignore these failures. 193 } 194 } 195 196 // Abandoning sessions created by the @LargeTest takes time. We must ensure that all 197 // sessions are abandoned before proceeding with the next test. If all the sessions are not 198 // abandoned before starting a new test, we may encounter an IllegalStateException 199 callback?.apply { 200 awaitSessionFinishCallbacks() 201 resetLatches() 202 installer.unregisterSessionCallback(this) 203 } 204 } 205 206 @After 207 fun revokeShellPermissionsAndCloseThread() { 208 instrumentation.uiAutomation.dropShellPermissionIdentity() 209 handlerThread?.quitSafely() 210 } 211 212 @Test 213 fun truncateAppLabel() { 214 val longLabel = invalidAppLabel() 215 val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply { 216 setAppLabel(longLabel) 217 } 218 219 createAndAbandonSession(params) { 220 assertThat(installer.getSessionInfo(it)?.appLabel) 221 .isEqualTo(longLabel.take(PackageItemInfo.MAX_SAFE_LABEL_LENGTH)) 222 } 223 } 224 225 @Test 226 fun removeInvalidAppPackageName() { 227 val longName = invalidPackageName() 228 val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply { 229 setAppPackageName(longName) 230 } 231 232 createAndAbandonSession(params) { 233 assertThat(installer.getSessionInfo(it)?.appPackageName) 234 .isEqualTo(null) 235 } 236 } 237 238 @Test 239 fun removeInvalidInstallerPackageName() { 240 val longName = invalidPackageName() 241 val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply { 242 setInstallerPackageName(longName) 243 } 244 245 createAndAbandonSession(params) { 246 // If a custom installer name is dropped, it defaults to the caller 247 assertThat(installer.getSessionInfo(it)?.installerPackageName) 248 .isEqualTo(context.packageName) 249 } 250 } 251 252 @Test 253 fun truncateWhitelistPermissions() { 254 val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply { 255 setWhitelistedRestrictedPermissions(invalidPermissions()) 256 } 257 258 createAndAbandonSession(params) { 259 assertThat(installer.getSessionInfo(it)?.whitelistedRestrictedPermissions!!) 260 .containsExactlyElementsIn(RESTRICTED_PERMISSIONS) 261 } 262 } 263 264 @Test 265 fun fillPackageNameWithParsedValue() { 266 val params = SessionParams(SessionParams.MODE_FULL_INSTALL) 267 val sessionId = installer.createSession(params) 268 val session = installer.openSession(sessionId) 269 270 javaClass.classLoader.getResourceAsStream("PackageManagerTestAppVersion1.apk")!! 271 .use { input -> 272 session.openWrite("base", 0, -1) 273 .use { output -> input.copyTo(output) } 274 } 275 276 // Test instrumentation doesn't have install permissions, so use shell 277 ShellIdentityUtils.invokeWithShellPermissions { 278 session.commit(receiver.makeIntentSender(sessionId)) 279 } 280 session.close() 281 282 // The actual contents aren't verified as part of this test. Only care about it finishing. 283 val result = receiver.getResult(TimeUnit.SECONDS, 30) 284 assertThat(result).isNotNull() 285 286 val sessionInfo = installer.getSessionInfo(sessionId) 287 assertThat(sessionInfo).isNotNull() 288 assertThat(sessionInfo!!.getAppPackageName()).isEqualTo(TEST_PKG_NAME) 289 } 290 291 @LargeTest 292 @Test 293 fun allocateMaxSessionsWithPermission() { 294 // Already invoking with shell permissions. Using the function to setup the handler 295 setupHandlerAndPermissions(/* Need permissions? */false) 296 297 callback = SessionStatusTrackerCallback(sessionFinishCount = 1024) 298 299 installer.registerSessionCallback(callback!!, handler!!) 300 301 ShellIdentityUtils.invokeWithShellPermissions { 302 repeat(1024) { createDummySession() } 303 assertThrows(IllegalStateException::class.java) { createDummySession() } 304 } 305 } 306 307 @LargeTest 308 @Test 309 fun allocateMaxSessionsNoPermission() { 310 setupHandlerAndPermissions(/* Need permissions? */false) 311 312 callback = SessionStatusTrackerCallback(sessionFinishCount = 50) 313 314 installer.registerSessionCallback(callback!!, handler!!) 315 316 repeat(50) { createDummySession() } 317 assertThrows(IllegalStateException::class.java) { createDummySession() } 318 } 319 320 @Test 321 fun whenGrantedInstallPermission_sessionIsActive() { 322 setupHandlerAndPermissions(true) 323 324 val params = SessionParams(SessionParams.MODE_FULL_INSTALL) 325 params.setRequireUserAction(SessionParams.USER_ACTION_REQUIRED) 326 327 callback = SessionStatusTrackerCallback(sessionActiveCount = 2, sessionInactiveCount = 1) 328 329 installer.registerSessionCallback(callback!!, handler!!) 330 331 val sessionId = installer.createSession(params) 332 // sessionActiveLatch counts down once when a session is opened 333 val session = installer.openSession(sessionId) 334 335 javaClass.classLoader.getResourceAsStream("PackageManagerTestAppVersion1.apk")!! 336 .use { input -> 337 session.openWrite("base", 0, -1) 338 .use { output -> input.copyTo(output) } 339 } 340 341 session.commit(receiver.makeIntentSender(sessionId)) 342 session.close() 343 344 // Wait till we get one session change callback to with status 'inactive' 345 callback?.awaitSessionInactiveCallbacks() 346 347 val installStatus = receiver.getResult(TimeUnit.SECONDS, 30) 348 .getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) 349 350 if (installStatus == PackageInstaller.STATUS_PENDING_USER_ACTION) { 351 installer.setPermissionsResult(sessionId, true) 352 } else { 353 fail("Did not wait for user action") 354 } 355 356 // sessionActiveLatch counts down the second time when install permission is granted. 357 // At this time, the latch opens. 358 callback?.awaitSessionActiveCallbacks() 359 assertThat(installer.getSessionInfo(sessionId)!!.isActive).isEqualTo(true) 360 } 361 362 @Test 363 fun whenWaitingForUserAction_sessionIsInactive() { 364 setupHandlerAndPermissions(true) 365 366 val params = SessionParams(SessionParams.MODE_FULL_INSTALL) 367 params.setRequireUserAction(SessionParams.USER_ACTION_REQUIRED) 368 369 callback = SessionStatusTrackerCallback(sessionActiveCount = 1, sessionInactiveCount = 1) 370 371 installer.registerSessionCallback(callback!!, handler!!) 372 373 val sessionId = installer.createSession(params) 374 val session = installer.openSession(sessionId) 375 376 // Wait till we get one session change callback to with status 'active' 377 callback?.awaitSessionActiveCallbacks() 378 379 javaClass.classLoader.getResourceAsStream("PackageManagerTestAppVersion1.apk")!! 380 .use { input -> 381 session.openWrite("base", 0, -1) 382 .use { output -> input.copyTo(output) } 383 } 384 385 session.commit(receiver.makeIntentSender(sessionId)) 386 session.close() 387 388 // Wait till we get one session change callback to with status 'inactive' 389 callback?.awaitSessionInactiveCallbacks() 390 391 val installStatus = receiver.getResult(TimeUnit.SECONDS, 30) 392 .getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) 393 394 assertThat(installStatus).isEqualTo(PackageInstaller.STATUS_PENDING_USER_ACTION) 395 assertThat(installer.getSessionInfo(sessionId)!!.isActive).isEqualTo(false) 396 } 397 398 private fun createDummySession() { 399 installer.createSession(SessionParams(SessionParams.MODE_FULL_INSTALL) 400 .apply { 401 setAppPackageName(invalidPackageName()) 402 setAppLabel(invalidAppLabel()) 403 setWhitelistedRestrictedPermissions(invalidPermissions()) 404 }) 405 } 406 407 private fun invalidPackageName(maxLength: Int = SessionParams.MAX_PACKAGE_NAME_LENGTH): String { 408 return (0 until (maxLength + 10)) 409 .asSequence() 410 .mapIndexed { index, _ -> 411 // A package name needs at least one separator 412 if (index == 2) { 413 '.' 414 } else { 415 Random.nextInt('z' - 'a').toChar() + 'a'.toInt() 416 } 417 } 418 .joinToString(separator = "") 419 } 420 421 private fun invalidAppLabel() = (0 until PackageItemInfo.MAX_SAFE_LABEL_LENGTH + 10) 422 .asSequence() 423 .map { Random.nextInt(Char.MAX_VALUE.toInt()).toChar() } 424 .joinToString(separator = "") 425 426 private fun invalidPermissions() = RESTRICTED_PERMISSIONS.toMutableSet() 427 .apply { 428 // Add some invalid permission names 429 repeat(10) { add(invalidPackageName(300)) } 430 } 431 432 private fun createAndAbandonSession(params: SessionParams, block: (Int) -> Unit = {}) { 433 val sessionId = installer.createSession(params) 434 try { 435 block(sessionId) 436 } finally { 437 installer.abandonSession(sessionId) 438 } 439 } 440 441 private fun setupHandlerAndPermissions(needPermissions: Boolean) { 442 if (needPermissions) { 443 instrumentation.uiAutomation.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES, 444 Manifest.permission.REQUEST_INSTALL_PACKAGES, 445 Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION) 446 } 447 handlerThread = HandlerThread("PackageSessionTests") 448 handlerThread?.let { 449 it.start() 450 handler = Handler(it.looper) 451 } 452 } 453 } 454