Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c1a7baba authored by Sumedh Sen's avatar Sumedh Sen
Browse files

Testing status of session after requiring user action

Test: atest
FrameworksCorePackageInstallerSessionsTests: PackageSessionTest
Bug: b/193606242
Change-Id: I098f4f8409e84ba36d60ba3a6fa380862ee7aca4
parent 8276afa0
Loading
Loading
Loading
Loading
+202 −8
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package android.content.pm
package android.content.pm


import android.Manifest
import android.app.Instrumentation
import android.app.Instrumentation
import android.app.PendingIntent
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.BroadcastReceiver
@@ -23,18 +24,23 @@ import android.content.Context
import android.content.Intent
import android.content.Intent
import android.content.IntentFilter
import android.content.IntentFilter
import android.content.pm.PackageInstaller.SessionParams
import android.content.pm.PackageInstaller.SessionParams
import android.os.Handler
import android.os.HandlerThread
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.Presubmit
import android.util.Log
import androidx.test.InstrumentationRegistry
import androidx.test.InstrumentationRegistry
import androidx.test.filters.LargeTest
import androidx.test.filters.LargeTest
import com.android.compatibility.common.util.ShellIdentityUtils
import com.android.compatibility.common.util.ShellIdentityUtils
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.testng.Assert.assertThrows
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit
import kotlin.random.Random
import kotlin.random.Random
import org.junit.After
import org.testng.Assert.assertThrows
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test


/**
/**
 * For verifying public [PackageInstaller] session APIs. This differs from
 * For verifying public [PackageInstaller] session APIs. This differs from
@@ -65,27 +71,98 @@ class PackageSessionTests {
        private const val INTENT_ACTION = "com.android.server.pm.test.test_app.action"
        private const val INTENT_ACTION = "com.android.server.pm.test.test_app.action"
    }
    }


    private val TAG = "PackageSessionTests"
    private val context: Context = InstrumentationRegistry.getContext()
    private val context: Context = InstrumentationRegistry.getContext()
    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()

    private val installer = context.packageManager.packageInstaller
    private val installer = context.packageManager.packageInstaller
    private var callback: SessionStatusTrackerCallback? = null
    private var handlerThread: HandlerThread? = null
    private var handler: Handler? = null


    private val receiver = object : BroadcastReceiver() {
    private val receiver = object : BroadcastReceiver() {
        private val results = ArrayBlockingQueue<Intent>(1)
        private val results = ArrayBlockingQueue<Intent>(1)


        override fun onReceive(context: Context, intent: Intent) {
        override fun onReceive(context: Context, intent: Intent) {
            // Added as a safety net. Have observed instances where the Queue isn't empty which
            // causes the test suite to crash.
            if (results.size != 0) {
                clear()
            }
            results.add(intent)
            results.add(intent)
        }
        }


        fun makeIntentSender(sessionId: Int) = PendingIntent.getBroadcast(context, sessionId,
        fun makeIntentSender(sessionId: Int) = PendingIntent.getBroadcast(context, sessionId,
                Intent(INTENT_ACTION),
                Intent(INTENT_ACTION),
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE_UNAUDITED).intentSender
                PendingIntent.FLAG_UPDATE_CURRENT
                        or PendingIntent.FLAG_MUTABLE_UNAUDITED).intentSender


        fun getResult(unit: TimeUnit, timeout: Long) = results.poll(timeout, unit)
        fun getResult(unit: TimeUnit, timeout: Long) = results.poll(timeout, unit)


        fun clear() = results.clear()
        fun clear() = results.clear()
    }
    }


    class SessionStatusTrackerCallback : PackageInstaller.SessionCallback {
        private val TAG: String = "SessionStatusTrackerCallback"
        private val DEFAULT_TIMEOUT: Long = 30
        private var mSessionActiveLatch: CountDownLatch? = null
        private var mSessionInactiveLatch: CountDownLatch? = null
        private var mSessionFinishLatch: CountDownLatch? = null
        private val mSessionIds = mutableSetOf<Int>()

        constructor(sessionActiveCount: Int = 0, sessionInactiveCount: Int = 0) {
            this.mSessionActiveLatch = CountDownLatch(sessionActiveCount)
            this.mSessionInactiveLatch = CountDownLatch(sessionInactiveCount)
        }

        constructor(sessionFinishCount: Int = 0) {
            this.mSessionFinishLatch = CountDownLatch(sessionFinishCount)
        }

        override fun onCreated(sessionId: Int) {
            mSessionIds.add(sessionId)
        }

        override fun onBadgingChanged(sessionId: Int) {}

        override fun onActiveChanged(sessionId: Int, active: Boolean) {
            if (mSessionIds.contains(sessionId)) {
                if (active) {
                    mSessionActiveLatch?.countDown()
                } else {
                    mSessionInactiveLatch?.countDown()
                }
            } else {
                Log.d(TAG, "Did not find session ID $sessionId which was opened.")
            }
        }

        override fun onProgressChanged(sessionId: Int, progress: Float) {}

        override fun onFinished(sessionId: Int, success: Boolean) {
            if (!success and mSessionIds.contains(sessionId)) {
                mSessionFinishLatch?.countDown()
            }
        }

        fun awaitSessionActiveCallbacks() {
            mSessionActiveLatch?.await(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
        }

        fun awaitSessionInactiveCallbacks() {
            mSessionInactiveLatch?.await(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
        }

        fun awaitSessionFinishCallbacks() {
            mSessionFinishLatch?.await(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
        }

        fun resetLatches() {
            mSessionActiveLatch = null
            mSessionInactiveLatch = null
            mSessionFinishLatch = null
        }
    }

    @Before
    @Before
    fun registerReceiver() {
    fun registerReceiver() {
        receiver.clear()
        receiver.clear()
@@ -101,8 +178,7 @@ class PackageSessionTests {
    @After
    @After
    fun abandonAllSessions() {
    fun abandonAllSessions() {
        instrumentation.uiAutomation
        instrumentation.uiAutomation
                .executeShellCommand("pm uninstall com.android.server.pm.test.test_app")
                .executeShellCommand("pm uninstall $TEST_PKG_NAME")
                .close()


        installer.mySessions.asSequence()
        installer.mySessions.asSequence()
                .map { it.sessionId }
                .map { it.sessionId }
@@ -110,11 +186,27 @@ class PackageSessionTests {
                    try {
                    try {
                        installer.abandonSession(it)
                        installer.abandonSession(it)
                    } catch (ignored: Exception) {
                    } catch (ignored: Exception) {
                        Log.e(TAG, "Abandon failed: ", ignored)
                        // Querying for sessions checks by calling package name, but abandoning
                        // Querying for sessions checks by calling package name, but abandoning
                        // checks by UID, which won't match if this test failed to clean up
                        // checks by UID, which won't match if this test failed to clean up
                        // on a previous install + run + uninstall, so ignore these failures.
                        // on a previous install + run + uninstall, so ignore these failures.
                    }
                    }
                }
                }

        // Abandoning sessions created by the @LargeTest takes time. We must ensure that all
        // sessions are abandoned before proceeding with the next test. If all the sessions are not
        // abandoned before starting a new test, we may encounter an IllegalStateException
        callback?.apply {
            awaitSessionFinishCallbacks()
            resetLatches()
            installer.unregisterSessionCallback(this)
        }
    }

    @After
    fun revokeShellPermissionsAndCloseThread() {
        instrumentation.uiAutomation.dropShellPermissionIdentity()
        handlerThread?.quitSafely()
    }
    }


    @Test
    @Test
@@ -199,6 +291,13 @@ class PackageSessionTests {
    @LargeTest
    @LargeTest
    @Test
    @Test
    fun allocateMaxSessionsWithPermission() {
    fun allocateMaxSessionsWithPermission() {
        // Already invoking with shell permissions. Using the function to setup the handler
        setupHandlerAndPermissions(/* Need permissions? */false)

        callback = SessionStatusTrackerCallback(sessionFinishCount = 1024)

        installer.registerSessionCallback(callback!!, handler!!)

        ShellIdentityUtils.invokeWithShellPermissions {
        ShellIdentityUtils.invokeWithShellPermissions {
            repeat(1024) { createDummySession() }
            repeat(1024) { createDummySession() }
            assertThrows(IllegalStateException::class.java) { createDummySession() }
            assertThrows(IllegalStateException::class.java) { createDummySession() }
@@ -208,10 +307,94 @@ class PackageSessionTests {
    @LargeTest
    @LargeTest
    @Test
    @Test
    fun allocateMaxSessionsNoPermission() {
    fun allocateMaxSessionsNoPermission() {
        setupHandlerAndPermissions(/* Need permissions? */false)

        callback = SessionStatusTrackerCallback(sessionFinishCount = 50)

        installer.registerSessionCallback(callback!!, handler!!)

        repeat(50) { createDummySession() }
        repeat(50) { createDummySession() }
        assertThrows(IllegalStateException::class.java) { createDummySession() }
        assertThrows(IllegalStateException::class.java) { createDummySession() }
    }
    }


    @Test
    fun whenGrantedInstallPermission_sessionIsActive() {
        setupHandlerAndPermissions(true)

        val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
        params.setRequireUserAction(SessionParams.USER_ACTION_REQUIRED)

        callback = SessionStatusTrackerCallback(sessionActiveCount = 2, sessionInactiveCount = 1)

        installer.registerSessionCallback(callback!!, handler!!)

        val sessionId = installer.createSession(params)
        // sessionActiveLatch counts down once when a session is opened
        val session = installer.openSession(sessionId)

        javaClass.classLoader.getResourceAsStream("PackageManagerTestAppVersion1.apk")!!
                .use { input ->
                    session.openWrite("base", 0, -1)
                            .use { output -> input.copyTo(output) }
                }

        session.commit(receiver.makeIntentSender(sessionId))
        session.close()

        // Wait till we get one session change callback to with status 'inactive'
        callback?.awaitSessionInactiveCallbacks()

        val installStatus = receiver.getResult(TimeUnit.SECONDS, 30)
                .getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)

        if (installStatus == PackageInstaller.STATUS_PENDING_USER_ACTION) {
            installer.setPermissionsResult(sessionId, true)
        } else {
            fail("Did not wait for user action")
        }

        // sessionActiveLatch counts down the second time when install permission is granted.
        // At this time, the latch opens.
        callback?.awaitSessionActiveCallbacks()
        assertThat(installer.getSessionInfo(sessionId)!!.isActive).isEqualTo(true)
    }

    @Test
    fun whenWaitingForUserAction_sessionIsInactive() {
        setupHandlerAndPermissions(true)

        val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
        params.setRequireUserAction(SessionParams.USER_ACTION_REQUIRED)

        callback = SessionStatusTrackerCallback(sessionActiveCount = 1, sessionInactiveCount = 1)

        installer.registerSessionCallback(callback!!, handler!!)

        val sessionId = installer.createSession(params)
        val session = installer.openSession(sessionId)

        // Wait till we get one session change callback to with status 'active'
        callback?.awaitSessionActiveCallbacks()

        javaClass.classLoader.getResourceAsStream("PackageManagerTestAppVersion1.apk")!!
                .use { input ->
                    session.openWrite("base", 0, -1)
                            .use { output -> input.copyTo(output) }
                }

        session.commit(receiver.makeIntentSender(sessionId))
        session.close()

        // Wait till we get one session change callback to with status 'inactive'
        callback?.awaitSessionInactiveCallbacks()

        val installStatus = receiver.getResult(TimeUnit.SECONDS, 30)
                .getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)

        assertThat(installStatus).isEqualTo(PackageInstaller.STATUS_PENDING_USER_ACTION)
        assertThat(installer.getSessionInfo(sessionId)!!.isActive).isEqualTo(false)
    }

    private fun createDummySession() {
    private fun createDummySession() {
        installer.createSession(SessionParams(SessionParams.MODE_FULL_INSTALL)
        installer.createSession(SessionParams(SessionParams.MODE_FULL_INSTALL)
                .apply {
                .apply {
@@ -254,4 +437,15 @@ class PackageSessionTests {
            installer.abandonSession(sessionId)
            installer.abandonSession(sessionId)
        }
        }
    }
    }

    private fun setupHandlerAndPermissions(needPermissions: Boolean) {
        if (needPermissions) {
            instrumentation.uiAutomation.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES,
                    Manifest.permission.REQUEST_INSTALL_PACKAGES,
                    Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION)
        }
        handlerThread = HandlerThread("PackageSessionTests")
        handlerThread?.start()
        handler = Handler(handlerThread?.looper)
    }
}
}