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

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21019194

Change-Id: Ie5e6ff4d55e5a229df263370be4afdc07a17f762
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Marcello Galhardo
2023-01-26 10:34:46 +00:00
committed by Automerger Merge Worker
4 changed files with 57 additions and 213 deletions

View File

@@ -17,10 +17,12 @@
package com.android.systemui.notetask package com.android.systemui.notetask
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.ActivityNotFoundException
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.UserManager import android.os.UserManager
import android.util.Log
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.util.kotlin.getOrNull 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 * 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. * 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. * collapsed if the notes bubble is already opened.
* *
* That will let users open other apps in full screen, and take contextual notes. * That will let users open other apps in full screen, and take contextual notes.
@@ -68,16 +70,23 @@ constructor(
val bubbles = optionalBubbles.getOrNull() ?: return val bubbles = optionalBubbles.getOrNull() ?: return
val keyguardManager = optionalKeyguardManager.getOrNull() ?: return val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
val userManager = optionalUserManager.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. // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return if (!userManager.isUserUnlocked) return
if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) { val intent = intentResolver.resolveIntent() ?: return
context.startActivity(intent)
} else { // TODO(b/266686199): We should handle when app not available. For now, we log.
// TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter. try {
bubbles.showOrHideAppBubble(intent) if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
context.startActivity(intent)
} else {
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 { companion object {
private val TAG = NoteTaskController::class.simpleName.orEmpty()
// TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead. // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
const val NOTE_TASK_KEY_EVENT = 311 const val NOTE_TASK_KEY_EVENT = 311
} }

View File

@@ -16,70 +16,39 @@
package com.android.systemui.notetask 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.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 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 internal class NoteTaskIntentResolver
@Inject @Inject
constructor( constructor(
private val packageManager: PackageManager, private val context: Context,
private val roleManager: RoleManager,
) { ) {
fun resolveIntent(): Intent? { fun resolveIntent(): Intent? {
val intent = Intent(ACTION_CREATE_NOTE) val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, context.user).firstOrNull()
val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
val infoList = packageManager.queryIntentActivities(intent, flags)
for (info in infoList) { if (packageName.isNullOrEmpty()) return null
val packageName = info.activityInfo.applicationInfo.packageName ?: continue
val activityName = resolveActivityNameForNotesAction(packageName) ?: continue
return Intent(ACTION_CREATE_NOTE) return Intent(ACTION_CREATE_NOTE)
.setPackage(packageName) .setPackage(packageName)
.setComponent(ComponentName(packageName, activityName)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) // 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)
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
} }
companion object { 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" const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
// TODO(b/265912743): Use RoleManager.NOTES_ROLE instead. // 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

View File

@@ -51,7 +51,7 @@ internal interface NoteTaskModule {
featureFlags: FeatureFlags, featureFlags: FeatureFlags,
roleManager: RoleManager, roleManager: RoleManager,
): Boolean { ): Boolean {
val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.NOTE_ROLE) val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.ROLE_NOTES)
val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS) val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
return isRoleAvailable && isFeatureEnabled return isRoleAvailable && isFeatureEnabled
} }

View File

@@ -16,17 +16,14 @@
package com.android.systemui.notetask package com.android.systemui.notetask
import android.content.ComponentName import android.app.role.RoleManager
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.content.pm.ResolveInfo
import android.test.suitebuilder.annotation.SmallTest import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4 import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE 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.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Before import org.junit.Before
@@ -47,172 +44,39 @@ import org.mockito.MockitoAnnotations
internal class NoteTaskIntentResolverTest : SysuiTestCase() { internal class NoteTaskIntentResolverTest : SysuiTestCase() {
@Mock lateinit var packageManager: PackageManager @Mock lateinit var packageManager: PackageManager
@Mock lateinit var roleManager: RoleManager
private lateinit var resolver: NoteTaskIntentResolver private lateinit var underTest: NoteTaskIntentResolver
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
resolver = NoteTaskIntentResolver(packageManager) underTest = NoteTaskIntentResolver(context, roleManager)
}
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 @Test
fun resolveIntent_shouldReturnNotesIntent() { fun resolveIntent_shouldReturnIntentInStylusMode() {
givenQueryIntentActivities { listOf(createResolveInfo()) } val packageName = "com.android.note.app"
givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo()) } whenever(roleManager.getRoleHoldersAsUser(NoteTaskIntentResolver.ROLE_NOTES, context.user))
.then { listOf(packageName) }
val actual = resolver.resolveIntent() val actual = underTest.resolveIntent()
val expected = requireNotNull(actual) { "Intent must not be null" }
Intent(ACTION_CREATE_NOTE) assertThat(actual.action).isEqualTo(ACTION_CREATE_NOTE)
.setPackage("PackageName") assertThat(actual.`package`).isEqualTo(packageName)
.setComponent(ComponentName("PackageName", "ActivityName")) val expectedExtra = actual.getExtra(NoteTaskIntentResolver.INTENT_EXTRA_USE_STYLUS_MODE)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) assertThat(expectedExtra).isEqualTo(true)
// Compares the string representation of both intents, as they are different instances. val expectedFlag = actual.flags and Intent.FLAG_ACTIVITY_NEW_TASK
assertThat(actual.toString()).isEqualTo(expected.toString()) assertThat(expectedFlag).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
} }
@Test @Test
fun resolveIntent_activityInfoEnabledIsFalse_shouldReturnNull() { fun resolveIntent_noRoleHolderIsSet_shouldReturnNull() {
givenQueryIntentActivities { listOf(createResolveInfo()) } whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskIntentResolver.ROLE_NOTES), any()))
givenResolveActivity { .then { listOf<String>() }
createResolveInfo(activityInfo = createActivityInfo(enabled = false))
}
val actual = resolver.resolveIntent() val actual = underTest.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()
}
@Test
fun resolveIntent_activityInfoNameIsBlank_shouldReturnNull() {
givenQueryIntentActivities { listOf(createResolveInfo()) }
givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = "")) }
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()
}
@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()
}
@Test
fun resolveIntent_activityNotFoundForAction_shouldReturnNull() {
givenQueryIntentActivities { emptyList() }
val actual = resolver.resolveIntent()
assertThat(actual).isNull() assertThat(actual).isNull()
} }