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

Commit c07303e0 authored by Marcello Galhardo's avatar Marcello Galhardo Committed by Automerger Merge Worker
Browse files

Merge "Use Note Role Holder when opening Note Task" into tm-qpr-dev am: 1a1482df

parents d5817a77 1a1482df
Loading
Loading
Loading
Loading
+18 −7
Original line number Diff line number Diff line
@@ -17,10 +17,12 @@
package com.android.systemui.notetask

import android.app.KeyguardManager
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.os.UserManager
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.util.kotlin.getOrNull
@@ -57,7 +59,7 @@ constructor(
     * If the keyguard is locked, notes will open as a full screen experience. A locked device has
     * no contextual information which let us use the whole screen space available.
     *
     * If no in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be
     * If not in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be
     * collapsed if the notes bubble is already opened.
     *
     * That will let users open other apps in full screen, and take contextual notes.
@@ -68,17 +70,24 @@ constructor(
        val bubbles = optionalBubbles.getOrNull() ?: return
        val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
        val userManager = optionalUserManager.getOrNull() ?: return
        val intent = intentResolver.resolveIntent() ?: return

        // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
        if (!userManager.isUserUnlocked) return

        val intent = intentResolver.resolveIntent() ?: return

        // TODO(b/266686199): We should handle when app not available. For now, we log.
        try {
            if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
                context.startActivity(intent)
            } else {
            // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
                bubbles.showOrHideAppBubble(intent)
            }
        } catch (e: ActivityNotFoundException) {
            val message =
                "Activity not found for action: ${NoteTaskIntentResolver.ACTION_CREATE_NOTE}."
            Log.e(TAG, message, e)
        }
    }

    /**
@@ -106,6 +115,8 @@ constructor(
    }

    companion object {
        private val TAG = NoteTaskController::class.simpleName.orEmpty()

        // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
        const val NOTE_TASK_KEY_EVENT = 311
    }
+17 −48
Original line number Diff line number Diff line
@@ -16,70 +16,39 @@

package com.android.systemui.notetask

import android.content.ComponentName
import android.app.role.RoleManager
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
import javax.inject.Inject

/**
 * Class responsible to query all apps and find one that can handle the [ACTION_CREATE_NOTE]. If
 * found, an [Intent] ready for be launched will be returned. Otherwise, returns null.
 *
 * TODO(b/248274123): should be revisited once the notes role is implemented.
 */
internal class NoteTaskIntentResolver
@Inject
constructor(
    private val packageManager: PackageManager,
    private val context: Context,
    private val roleManager: RoleManager,
) {

    fun resolveIntent(): Intent? {
        val intent = Intent(ACTION_CREATE_NOTE)
        val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
        val infoList = packageManager.queryIntentActivities(intent, flags)
        val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, context.user).firstOrNull()

        for (info in infoList) {
            val packageName = info.activityInfo.applicationInfo.packageName ?: continue
            val activityName = resolveActivityNameForNotesAction(packageName) ?: continue
        if (packageName.isNullOrEmpty()) return null

        return Intent(ACTION_CREATE_NOTE)
            .setPackage(packageName)
                .setComponent(ComponentName(packageName, activityName))
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }

        return null
    }

    private fun resolveActivityNameForNotesAction(packageName: String): String? {
        val intent = Intent(ACTION_CREATE_NOTE).setPackage(packageName)
        val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
        val resolveInfo = packageManager.resolveActivity(intent, flags)

        val activityInfo = resolveInfo?.activityInfo ?: return null
        if (activityInfo.name.isNullOrBlank()) return null
        if (!activityInfo.exported) return null
        if (!activityInfo.enabled) return null
        if (!activityInfo.showWhenLocked) return null
        if (!activityInfo.turnScreenOn) return null

        return activityInfo.name
            // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint was
            // used to start it.
            .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
    }

    companion object {
        // TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead.
        // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
        const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"

        // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
        const val NOTE_ROLE = "android.app.role.NOTES"
        const val ROLE_NOTES = "android.app.role.NOTES"

        // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
        const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
    }
}

private val ActivityInfo.showWhenLocked: Boolean
    get() = flags and ActivityInfo.FLAG_SHOW_WHEN_LOCKED != 0

private val ActivityInfo.turnScreenOn: Boolean
    get() = flags and ActivityInfo.FLAG_TURN_SCREEN_ON != 0
+1 −1
Original line number Diff line number Diff line
@@ -51,7 +51,7 @@ internal interface NoteTaskModule {
            featureFlags: FeatureFlags,
            roleManager: RoleManager,
        ): Boolean {
            val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.NOTE_ROLE)
            val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.ROLE_NOTES)
            val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
            return isRoleAvailable && isFeatureEnabled
        }
+21 −157
Original line number Diff line number Diff line
@@ -16,17 +16,14 @@

package com.android.systemui.notetask

import android.content.ComponentName
import android.app.role.RoleManager
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.content.pm.ResolveInfo
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -47,172 +44,39 @@ import org.mockito.MockitoAnnotations
internal class NoteTaskIntentResolverTest : SysuiTestCase() {

    @Mock lateinit var packageManager: PackageManager
    @Mock lateinit var roleManager: RoleManager

    private lateinit var resolver: NoteTaskIntentResolver
    private lateinit var underTest: NoteTaskIntentResolver

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        resolver = NoteTaskIntentResolver(packageManager)
    }

    private fun createResolveInfo(
        activityInfo: ActivityInfo? = createActivityInfo(),
    ): ResolveInfo {
        return ResolveInfo().apply { this.activityInfo = activityInfo }
    }

    private fun createActivityInfo(
        packageName: String = "PackageName",
        name: String? = "ActivityName",
        exported: Boolean = true,
        enabled: Boolean = true,
        showWhenLocked: Boolean = true,
        turnScreenOn: Boolean = true,
    ): ActivityInfo {
        return ActivityInfo().apply {
            this.name = name
            this.exported = exported
            this.enabled = enabled
            if (showWhenLocked) {
                flags = flags or ActivityInfo.FLAG_SHOW_WHEN_LOCKED
            }
            if (turnScreenOn) {
                flags = flags or ActivityInfo.FLAG_TURN_SCREEN_ON
            }
            this.applicationInfo = ApplicationInfo().apply { this.packageName = packageName }
        }
    }

    private fun givenQueryIntentActivities(block: () -> List<ResolveInfo>) {
        whenever(packageManager.queryIntentActivities(any(), any<ResolveInfoFlags>()))
            .thenReturn(block())
    }

    private fun givenResolveActivity(block: () -> ResolveInfo?) {
        whenever(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenReturn(block())
    }

    @Test
    fun resolveIntent_shouldReturnNotesIntent() {
        givenQueryIntentActivities { listOf(createResolveInfo()) }
        givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo()) }

        val actual = resolver.resolveIntent()

        val expected =
            Intent(ACTION_CREATE_NOTE)
                .setPackage("PackageName")
                .setComponent(ComponentName("PackageName", "ActivityName"))
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        // Compares the string representation of both intents, as they are different instances.
        assertThat(actual.toString()).isEqualTo(expected.toString())
    }

    @Test
    fun resolveIntent_activityInfoEnabledIsFalse_shouldReturnNull() {
        givenQueryIntentActivities { listOf(createResolveInfo()) }
        givenResolveActivity {
            createResolveInfo(activityInfo = createActivityInfo(enabled = false))
        }

        val actual = resolver.resolveIntent()

        assertThat(actual).isNull()
    }

    @Test
    fun resolveIntent_activityInfoExportedIsFalse_shouldReturnNull() {
        givenQueryIntentActivities { listOf(createResolveInfo()) }
        givenResolveActivity {
            createResolveInfo(activityInfo = createActivityInfo(exported = false))
        }

        val actual = resolver.resolveIntent()

        assertThat(actual).isNull()
    }

    @Test
    fun resolveIntent_activityInfoShowWhenLockedIsFalse_shouldReturnNull() {
        givenQueryIntentActivities { listOf(createResolveInfo()) }
        givenResolveActivity {
            createResolveInfo(activityInfo = createActivityInfo(showWhenLocked = false))
        }

        val actual = resolver.resolveIntent()

        assertThat(actual).isNull()
    }

    @Test
    fun resolveIntent_activityInfoTurnScreenOnIsFalse_shouldReturnNull() {
        givenQueryIntentActivities { listOf(createResolveInfo()) }
        givenResolveActivity {
            createResolveInfo(activityInfo = createActivityInfo(turnScreenOn = false))
        }

        val actual = resolver.resolveIntent()

        assertThat(actual).isNull()
        underTest = NoteTaskIntentResolver(context, roleManager)
    }

    @Test
    fun resolveIntent_activityInfoNameIsBlank_shouldReturnNull() {
        givenQueryIntentActivities { listOf(createResolveInfo()) }
        givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = "")) }
    fun resolveIntent_shouldReturnIntentInStylusMode() {
        val packageName = "com.android.note.app"
        whenever(roleManager.getRoleHoldersAsUser(NoteTaskIntentResolver.ROLE_NOTES, context.user))
            .then { listOf(packageName) }

        val actual = resolver.resolveIntent()

        assertThat(actual).isNull()
    }

    @Test
    fun resolveIntent_activityInfoNameIsNull_shouldReturnNull() {
        givenQueryIntentActivities { listOf(createResolveInfo()) }
        givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = null)) }

        val actual = resolver.resolveIntent()

        assertThat(actual).isNull()
    }
        val actual = underTest.resolveIntent()

    @Test
    fun resolveIntent_activityInfoIsNull_shouldReturnNull() {
        givenQueryIntentActivities { listOf(createResolveInfo()) }
        givenResolveActivity { createResolveInfo(activityInfo = null) }

        val actual = resolver.resolveIntent()

        assertThat(actual).isNull()
    }

    @Test
    fun resolveIntent_resolveActivityIsNull_shouldReturnNull() {
        givenQueryIntentActivities { listOf(createResolveInfo()) }
        givenResolveActivity { null }

        val actual = resolver.resolveIntent()

        assertThat(actual).isNull()
    }

    @Test
    fun resolveIntent_packageNameIsBlank_shouldReturnNull() {
        givenQueryIntentActivities {
            listOf(createResolveInfo(createActivityInfo(packageName = "")))
        }

        val actual = resolver.resolveIntent()

        assertThat(actual).isNull()
        requireNotNull(actual) { "Intent must not be null" }
        assertThat(actual.action).isEqualTo(ACTION_CREATE_NOTE)
        assertThat(actual.`package`).isEqualTo(packageName)
        val expectedExtra = actual.getExtra(NoteTaskIntentResolver.INTENT_EXTRA_USE_STYLUS_MODE)
        assertThat(expectedExtra).isEqualTo(true)
        val expectedFlag = actual.flags and Intent.FLAG_ACTIVITY_NEW_TASK
        assertThat(expectedFlag).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
    }

    @Test
    fun resolveIntent_activityNotFoundForAction_shouldReturnNull() {
        givenQueryIntentActivities { emptyList() }
    fun resolveIntent_noRoleHolderIsSet_shouldReturnNull() {
        whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskIntentResolver.ROLE_NOTES), any()))
            .then { listOf<String>() }

        val actual = resolver.resolveIntent()
        val actual = underTest.resolveIntent()

        assertThat(actual).isNull()
    }