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

Commit 799da223 authored by Behnam Heydarshahi's avatar Behnam Heydarshahi
Browse files

Alarm Tile UA Interactor check PendingIntent type

Fixes: 316343808
Flag: aconfig com.android.systemui.qs_new_tiles DEVELOPMENT
Test: atest SystemUiRoboTests
Test: atest AlarmTileUserActionInteractorTest
Test: atest QSTileIntentUserInputHandlerTest
Change-Id: I4817ce34dd503b556b862079e397012a7ba859c3
parent b23efa49
Loading
Loading
Loading
Loading
+114 −4
Original line number Diff line number Diff line
@@ -16,32 +16,45 @@

package com.android.systemui.qs.tiles.base.actions

import android.app.PendingIntent
import android.content.ComponentName
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.content.pm.ResolveInfo
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatcher
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidJUnit4::class)
class QSTileIntentUserInputHandlerTest : SysuiTestCase() {

    @Mock private lateinit var activityStarted: ActivityStarter
    @Mock private lateinit var packageManager: PackageManager
    @Mock private lateinit var activityStarter: ActivityStarter

    lateinit var underTest: QSTileIntentUserInputHandler

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        underTest = QSTileIntentUserInputHandlerImpl(activityStarted)
        underTest = QSTileIntentUserInputHandlerImpl(activityStarter, packageManager, user)
    }

    @Test
@@ -50,6 +63,103 @@ class QSTileIntentUserInputHandlerTest : SysuiTestCase() {

        underTest.handle(null, intent)

        verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
        verify(activityStarter).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
    }

    @Test
    fun testPassesActivityPendingIntentToStarterAsPendingIntent() {
        val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }

        underTest.handle(null, pendingIntent, true)

        verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any())
    }

    @Test
    fun testPassesActivityPendingIntentToStarterAsPendingIntentWhenNotRequestingActivityStart() {
        val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }

        underTest.handle(null, pendingIntent, false)

        verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any())
    }

    @Test
    fun testPassNonActivityPendingIntentAndRequestStartingActivity_findsIntentAndStarts() {
        val pendingIntent =
            mock<PendingIntent> {
                whenever(isActivity).thenReturn(false)
                whenever(creatorPackage).thenReturn(ORIGINAL_PACKAGE)
            }
        setUpQueryResult(listOf(createActivityInfo(testResolvedComponent, exported = true)))

        underTest.handle(null, pendingIntent, true)

        val expectedIntent =
            Intent(Intent.ACTION_MAIN)
                .addCategory(Intent.CATEGORY_LAUNCHER)
                .setPackage(null)
                .addFlags(
                    Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                )
                .setComponent(testResolvedComponent)

        verify(activityStarter)
            .postStartActivityDismissingKeyguard(
                argThat(IntentMatcher(expectedIntent)),
                eq(0),
                any()
            )
    }

    @Test
    fun testPassNonActivityPendingIntentAndDoNotRequestStartingActivity_doesNotStartActivity() {
        val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(false) }

        underTest.handle(null, pendingIntent, false)

        verify(activityStarter, never())
            .postStartActivityDismissingKeyguard(any(Intent::class.java), eq(0), any())
    }

    private fun createActivityInfo(
        componentName: ComponentName,
        exported: Boolean = false,
    ): ActivityInfo {
        return ActivityInfo().apply {
            packageName = componentName.packageName
            name = componentName.className
            this.exported = exported
        }
    }

    private fun setUpQueryResult(infos: List<ActivityInfo>) {
        `when`(
                packageManager.queryIntentActivitiesAsUser(
                    any(Intent::class.java),
                    any(ResolveInfoFlags::class.java),
                    eq(user.identifier)
                )
            )
            .thenReturn(infos.map { ResolveInfo().apply { activityInfo = it } })
    }

    private class IntentMatcher(intent: Intent) : ArgumentMatcher<Intent> {
        private val expectedIntent = intent
        override fun matches(argument: Intent?): Boolean {
            return argument?.action.equals(expectedIntent.action) &&
                argument?.`package`.equals(expectedIntent.`package`) &&
                argument?.component?.equals(expectedIntent.component)!! &&
                argument?.categories?.equals(expectedIntent.categories)!! &&
                argument?.flags?.equals(expectedIntent.flags)!!
        }
    }

    companion object {
        private const val ORIGINAL_PACKAGE = "original_pkg"
        private const val TEST_PACKAGE = "test_pkg"
        private const val TEST_COMPONENT_CLASS_NAME = "test_component_class_name"
        private val testResolvedComponent = ComponentName(TEST_PACKAGE, TEST_COMPONENT_CLASS_NAME)
        private val user = UserHandle.of(0)
    }
}
+11 −28
Original line number Diff line number Diff line
@@ -18,42 +18,25 @@ package com.android.systemui.qs.tiles.impl.alarm.domain.interactor

import android.app.AlarmManager.AlarmClockInfo
import android.app.PendingIntent
import android.content.Intent
import android.provider.AlarmClock
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.verify

@SmallTest
@RunWith(AndroidJUnit4::class)
class AlarmTileUserActionInteractorTest : SysuiTestCase() {
    private lateinit var activityStarter: ActivityStarter
    private lateinit var intentCaptor: ArgumentCaptor<Intent>
    private lateinit var pendingIntentCaptor: ArgumentCaptor<PendingIntent>

    lateinit var underTest: AlarmTileUserActionInteractor

    @Before
    fun setup() {
        activityStarter = mock<ActivityStarter>()
        intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
        pendingIntentCaptor = ArgumentCaptor.forClass(PendingIntent::class.java)
        underTest = AlarmTileUserActionInteractor(activityStarter)
    }
    private val inputHandler = FakeQSTileIntentUserInputHandler()
    private val underTest = AlarmTileUserActionInteractor(inputHandler)

    @Test
    fun handleClickWithDefaultIntent() = runTest {
@@ -62,21 +45,21 @@ class AlarmTileUserActionInteractorTest : SysuiTestCase() {

        underTest.handleInput(click(inputModel))

        verify(activityStarter)
            .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0), nullable())
        assertThat(intentCaptor.value.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
            assertThat(it.intent.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
        }
    }

    @Test
    fun handleClickWithPendingIntent() = runTest {
        val expectedIntent: PendingIntent = mock<PendingIntent>()
        val expectedIntent = mock<PendingIntent>()
        val alarmInfo = AlarmClockInfo(1L, expectedIntent)
        val inputModel = AlarmTileModel.NextAlarmSet(true, alarmInfo)

        underTest.handleInput(click(inputModel))

        verify(activityStarter)
            .postStartActivityDismissingKeyguard(capture(pendingIntentCaptor), nullable())
        assertThat(pendingIntentCaptor.value).isEqualTo(expectedIntent)
        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOnePendingIntentInput {
            assertThat(it.pendingIntent).isEqualTo(expectedIntent)
        }
    }
}
+49 −17
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.qs.tiles.base.actions

import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageManager
import android.os.UserHandle
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
@@ -32,13 +34,23 @@ import javax.inject.Inject
interface QSTileIntentUserInputHandler {

    fun handle(view: View?, intent: Intent)
    fun handle(view: View?, pendingIntent: PendingIntent)

    /** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
    fun handle(
        view: View?,
        pendingIntent: PendingIntent,
        requestLaunchingDefaultActivity: Boolean = false
    )
}

@SysUISingleton
class QSTileIntentUserInputHandlerImpl
@Inject
constructor(private val activityStarter: ActivityStarter) : QSTileIntentUserInputHandler {
constructor(
    private val activityStarter: ActivityStarter,
    private val packageManager: PackageManager,
    private val userHandle: UserHandle,
) : QSTileIntentUserInputHandler {

    override fun handle(view: View?, intent: Intent) {
        val animationController: ActivityLaunchAnimator.Controller? =
@@ -52,10 +64,12 @@ constructor(private val activityStarter: ActivityStarter) : QSTileIntentUserInpu
    }

    // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
    override fun handle(view: View?, pendingIntent: PendingIntent) {
        if (!pendingIntent.isActivity) {
            return
        }
    override fun handle(
        view: View?,
        pendingIntent: PendingIntent,
        requestLaunchingDefaultActivity: Boolean
    ) {
        if (pendingIntent.isActivity) {
            val animationController: ActivityLaunchAnimator.Controller? =
                view?.let {
                    ActivityLaunchAnimator.Controller.fromView(
@@ -63,10 +77,28 @@ constructor(private val activityStarter: ActivityStarter) : QSTileIntentUserInpu
                        InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
                    )
                }
        activityStarter.startPendingIntentMaybeDismissingKeyguard(
            pendingIntent,
            null,
            animationController
            activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
        } else if (requestLaunchingDefaultActivity) {
            val intent =
                Intent(Intent.ACTION_MAIN)
                    .addCategory(Intent.CATEGORY_LAUNCHER)
                    .setPackage(pendingIntent.creatorPackage)
                    .addFlags(
                        Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                    )
            val intents =
                packageManager.queryIntentActivitiesAsUser(
                    intent,
                    PackageManager.ResolveInfoFlags.of(0L),
                    userHandle.identifier
                )
            intents
                .firstOrNull { it.activityInfo.exported }
                ?.let { resolved ->
                    intent.setPackage(null)
                    intent.setComponent(resolved.activityInfo.componentName)
                    handle(view, intent)
                }
        }
    }
}
+4 −20
Original line number Diff line number Diff line
@@ -18,9 +18,7 @@ package com.android.systemui.qs.tiles.impl.alarm.domain.interactor

import android.content.Intent
import android.provider.AlarmClock
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
@@ -31,34 +29,20 @@ import javax.inject.Inject
class AlarmTileUserActionInteractor
@Inject
constructor(
    private val activityStarter: ActivityStarter,
    private val inputHandler: QSTileIntentUserInputHandler,
) : QSTileUserActionInteractor<AlarmTileModel> {
    override suspend fun handleInput(input: QSTileInput<AlarmTileModel>): Unit =
        with(input) {
            when (action) {
                is QSTileUserAction.Click -> {
                    val animationController =
                        action.view?.let {
                            ActivityLaunchAnimator.Controller.fromView(
                                it,
                                InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
                            )
                        }
                    if (
                        data is AlarmTileModel.NextAlarmSet &&
                            data.alarmClockInfo.showIntent != null
                    ) {
                        val pendingIndent = data.alarmClockInfo.showIntent
                        activityStarter.postStartActivityDismissingKeyguard(
                            pendingIndent,
                            animationController
                        )
                        inputHandler.handle(action.view, pendingIndent, true)
                    } else {
                        activityStarter.postStartActivityDismissingKeyguard(
                            Intent(AlarmClock.ACTION_SHOW_ALARMS),
                            0,
                            animationController
                        )
                        inputHandler.handle(action.view, Intent(AlarmClock.ACTION_SHOW_ALARMS))
                    }
                }
                is QSTileUserAction.LongClick -> {}
+11 −4
Original line number Diff line number Diff line
@@ -35,14 +35,21 @@ class FakeQSTileIntentUserInputHandler : QSTileIntentUserInputHandler {
        mutableInputs.add(Input.Intent(view, intent))
    }

    override fun handle(view: View?, pendingIntent: PendingIntent) {
        mutableInputs.add(Input.PendingIntent(view, pendingIntent))
    override fun handle(
        view: View?,
        pendingIntent: PendingIntent,
        requestLaunchingDefaultActivity: Boolean
    ) {
        mutableInputs.add(Input.PendingIntent(view, pendingIntent, requestLaunchingDefaultActivity))
    }

    sealed interface Input {
        data class Intent(val view: View?, val intent: android.content.Intent) : Input
        data class PendingIntent(val view: View?, val pendingIntent: android.app.PendingIntent) :
            Input
        data class PendingIntent(
            val view: View?,
            val pendingIntent: android.app.PendingIntent,
            val requestLaunchingDefaultActivity: Boolean
        ) : Input
    }
}