Merge "Open Notes from a KeyEvent dispatch" into tm-qpr-dev

This commit is contained in:
Marcello Galhardo
2022-10-26 14:26:55 +00:00
committed by Android (Google) Code Review
14 changed files with 785 additions and 11 deletions

View File

@@ -289,6 +289,12 @@
<!-- Query all packages on device on R+ -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<queries>
<intent>
<action android:name="android.intent.action.NOTES" />
</intent>
</queries>
<!-- Permission to register process observer -->
<uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>

View File

@@ -48,6 +48,7 @@ import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent;
import com.android.systemui.notetask.NoteTaskModule;
import com.android.systemui.people.PeopleModule;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.privacy.PrivacyModule;
@@ -152,6 +153,7 @@ import dagger.Provides;
TunerModule.class,
UserModule.class,
UtilModule.class,
NoteTaskModule.class,
WalletModule.class
},
subcomponents = {

View File

@@ -325,6 +325,9 @@ object Flags {
// 1800 - shade container
@JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, true)
// 1900 - note task
@JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.notetask
import android.app.KeyguardManager
import android.content.Context
import android.os.UserManager
import android.view.KeyEvent
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.floating.FloatingTasks
import java.util.Optional
import javax.inject.Inject
/**
* Entry point for creating and managing note.
*
* The controller decides how a note is launched based in the device state: locked or unlocked.
*
* Currently, we only support a single task per time.
*/
@SysUISingleton
internal class NoteTaskController
@Inject
constructor(
private val context: Context,
private val intentResolver: NoteTaskIntentResolver,
private val optionalFloatingTasks: Optional<FloatingTasks>,
private val optionalKeyguardManager: Optional<KeyguardManager>,
private val optionalUserManager: Optional<UserManager>,
@NoteTaskEnabledKey private val isEnabled: Boolean,
) {
fun handleSystemKey(keyCode: Int) {
if (!isEnabled) return
if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
showNoteTask()
}
}
private fun showNoteTask() {
val floatingTasks = optionalFloatingTasks.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
if (keyguardManager.isKeyguardLocked) {
context.startActivity(intent)
} else {
// TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
floatingTasks.showOrSetStashed(intent)
}
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.notetask
import javax.inject.Qualifier
/** Key associated with a [Boolean] flag that enables or disables the note task feature. */
@Qualifier internal annotation class NoteTaskEnabledKey

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.notetask
import com.android.systemui.statusbar.CommandQueue
import com.android.wm.shell.floating.FloatingTasks
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
/** Class responsible to "glue" all note task dependencies. */
internal class NoteTaskInitializer
@Inject
constructor(
private val optionalFloatingTasks: Optional<FloatingTasks>,
private val lazyNoteTaskController: Lazy<NoteTaskController>,
private val commandQueue: CommandQueue,
@NoteTaskEnabledKey private val isEnabled: Boolean,
) {
private val callbacks =
object : CommandQueue.Callbacks {
override fun handleSystemKey(keyCode: Int) {
lazyNoteTaskController.get().handleSystemKey(keyCode)
}
}
fun initialize() {
if (isEnabled && optionalFloatingTasks.isPresent) {
commandQueue.addCallback(callbacks)
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.notetask
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 com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
import javax.inject.Inject
/**
* Class responsible to query all apps and find one that can handle the [NOTES_ACTION]. 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,
) {
fun resolveIntent(): Intent? {
val intent = Intent(NOTES_ACTION)
val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
val infoList = packageManager.queryIntentActivities(intent, flags)
for (info in infoList) {
val packageName = info.serviceInfo.applicationInfo.packageName ?: continue
val activityName = resolveActivityNameForNotesAction(packageName) ?: continue
return Intent(NOTES_ACTION)
.setComponent(ComponentName(packageName, activityName))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
return null
}
private fun resolveActivityNameForNotesAction(packageName: String): String? {
val intent = Intent(NOTES_ACTION).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 {
// TODO(b/254606432): Use Intent.ACTION_NOTES and Intent.ACTION_NOTES_LOCKED instead.
const val NOTES_ACTION = "android.intent.action.NOTES"
}
}
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

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.notetask
import android.app.KeyguardManager
import android.content.Context
import android.os.UserManager
import androidx.core.content.getSystemService
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import dagger.Module
import dagger.Provides
import java.util.*
/** Compose all dependencies required by Note Task feature. */
@Module
internal class NoteTaskModule {
@[Provides NoteTaskEnabledKey]
fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
return featureFlags.isEnabled(Flags.NOTE_TASKS)
}
@Provides
fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
return Optional.ofNullable(context.getSystemService())
}
@Provides
fun provideOptionalUserManager(context: Context): Optional<UserManager> {
return Optional.ofNullable(context.getSystemService())
}
}

View File

@@ -49,6 +49,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.notetask.NoteTaskInitializer;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.CommandQueue;
@@ -58,7 +59,6 @@ import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -113,7 +113,6 @@ public final class WMShell implements
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
private final Optional<FloatingTasks> mFloatingTasksOptional;
private final Optional<DesktopMode> mDesktopModeOptional;
private final CommandQueue mCommandQueue;
@@ -125,6 +124,7 @@ public final class WMShell implements
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final ProtoTracer mProtoTracer;
private final UserTracker mUserTracker;
private final NoteTaskInitializer mNoteTaskInitializer;
private final Executor mSysUiMainExecutor;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
@@ -176,7 +176,6 @@ public final class WMShell implements
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
Optional<FloatingTasks> floatingTasksOptional,
Optional<DesktopMode> desktopMode,
CommandQueue commandQueue,
ConfigurationController configurationController,
@@ -187,6 +186,7 @@ public final class WMShell implements
ProtoTracer protoTracer,
WakefulnessLifecycle wakefulnessLifecycle,
UserTracker userTracker,
NoteTaskInitializer noteTaskInitializer,
@Main Executor sysUiMainExecutor) {
mContext = context;
mShell = shell;
@@ -203,7 +203,7 @@ public final class WMShell implements
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
mUserTracker = userTracker;
mFloatingTasksOptional = floatingTasksOptional;
mNoteTaskInitializer = noteTaskInitializer;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -226,6 +226,8 @@ public final class WMShell implements
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
mDesktopModeOptional.ifPresent(this::initDesktopMode);
mNoteTaskInitializer.initialize();
}
@VisibleForTesting

View File

@@ -0,0 +1,166 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.notetask
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.os.UserManager
import android.test.suitebuilder.annotation.SmallTest
import android.view.KeyEvent
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.floating.FloatingTasks
import java.util.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
/**
* Tests for [NoteTaskController].
*
* Build/Install/Run:
* - atest SystemUITests:NoteTaskControllerTest
*/
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskControllerTest : SysuiTestCase() {
private val notesIntent = Intent(NOTES_ACTION)
@Mock lateinit var context: Context
@Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
@Mock lateinit var floatingTasks: FloatingTasks
@Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
@Mock lateinit var optionalUserManager: Optional<UserManager>
@Mock lateinit var userManager: UserManager
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
whenever(userManager.isUserUnlocked).thenReturn(true)
}
private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
return NoteTaskController(
context = context,
intentResolver = noteTaskIntentResolver,
optionalFloatingTasks = optionalFloatingTasks,
optionalKeyguardManager = optionalKeyguardManager,
optionalUserManager = optionalUserManager,
isEnabled = isEnabled,
)
}
@Test
fun handleSystemKey_keyguardIsLocked_shouldStartActivity() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context).startActivity(notesIntent)
verify(floatingTasks, never()).showOrSetStashed(notesIntent)
}
@Test
fun handleSystemKey_keyguardIsUnlocked_shouldStartFloatingTask() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(floatingTasks).showOrSetStashed(notesIntent)
verify(context, never()).startActivity(notesIntent)
}
@Test
fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
verify(context, never()).startActivity(notesIntent)
verify(floatingTasks, never()).showOrSetStashed(notesIntent)
}
@Test
fun handleSystemKey_floatingTasksIsNull_shouldDoNothing() {
whenever(optionalFloatingTasks.orElse(null)).thenReturn(null)
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
verify(floatingTasks, never()).showOrSetStashed(notesIntent)
}
@Test
fun handleSystemKey_keyguardManagerIsNull_shouldDoNothing() {
whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
verify(floatingTasks, never()).showOrSetStashed(notesIntent)
}
@Test
fun handleSystemKey_userManagerIsNull_shouldDoNothing() {
whenever(optionalUserManager.orElse(null)).thenReturn(null)
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
verify(floatingTasks, never()).showOrSetStashed(notesIntent)
}
@Test
fun handleSystemKey_intentResolverReturnsNull_shouldDoNothing() {
whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
verify(floatingTasks, never()).showOrSetStashed(notesIntent)
}
@Test
fun handleSystemKey_flagDisabled_shouldDoNothing() {
createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
verify(floatingTasks, never()).showOrSetStashed(notesIntent)
}
@Test
fun handleSystemKey_userIsLocked_shouldDoNothing() {
whenever(userManager.isUserUnlocked).thenReturn(false)
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
verify(floatingTasks, never()).showOrSetStashed(notesIntent)
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.notetask
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.floating.FloatingTasks
import java.util.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
/**
* Tests for [NoteTaskController].
*
* Build/Install/Run:
* - atest SystemUITests:NoteTaskInitializerTest
*/
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskInitializerTest : SysuiTestCase() {
@Mock lateinit var commandQueue: CommandQueue
@Mock lateinit var floatingTasks: FloatingTasks
@Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(optionalFloatingTasks.isPresent).thenReturn(true)
whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
}
private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
return NoteTaskInitializer(
optionalFloatingTasks = optionalFloatingTasks,
lazyNoteTaskController = mock(),
commandQueue = commandQueue,
isEnabled = isEnabled,
)
}
@Test
fun initialize_shouldAddCallbacks() {
createNoteTaskInitializer().initialize()
verify(commandQueue).addCallback(any())
}
@Test
fun initialize_flagDisabled_shouldDoNothing() {
createNoteTaskInitializer(isEnabled = false).initialize()
verify(commandQueue, never()).addCallback(any())
}
@Test
fun initialize_floatingTasksNotPresent_shouldDoNothing() {
whenever(optionalFloatingTasks.isPresent).thenReturn(false)
createNoteTaskInitializer().initialize()
verify(commandQueue, never()).addCallback(any())
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.notetask
import android.content.ComponentName
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.content.pm.ServiceInfo
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.MockitoAnnotations
/**
* Tests for [NoteTaskIntentResolver].
*
* Build/Install/Run:
* - atest SystemUITests:NoteTaskIntentResolverTest
*/
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskIntentResolverTest : SysuiTestCase() {
@Mock lateinit var packageManager: PackageManager
private lateinit var resolver: NoteTaskIntentResolver
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
resolver = NoteTaskIntentResolver(packageManager)
}
private fun createResolveInfo(
packageName: String = "PackageName",
activityInfo: ActivityInfo? = null,
): ResolveInfo {
return ResolveInfo().apply {
serviceInfo =
ServiceInfo().apply {
applicationInfo = ApplicationInfo().apply { this.packageName = packageName }
}
this.activityInfo = activityInfo
}
}
private fun createActivityInfo(
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
}
}
}
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(NOTES_ACTION)
.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()
}
@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(packageName = "")) }
val actual = resolver.resolveIntent()
assertThat(actual).isNull()
}
@Test
fun resolveIntent_activityNotFoundForAction_shouldReturnNull() {
givenQueryIntentActivities { emptyList() }
val actual = resolver.resolveIntent()
assertThat(actual).isNull()
}
}

View File

@@ -28,6 +28,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.notetask.NoteTaskInitializer;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -36,7 +37,6 @@ import com.android.systemui.tracing.ProtoTracer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -78,18 +78,31 @@ public class WMShellTest extends SysuiTestCase {
@Mock ProtoTracer mProtoTracer;
@Mock UserTracker mUserTracker;
@Mock ShellExecutor mSysUiMainExecutor;
@Mock FloatingTasks mFloatingTasks;
@Mock NoteTaskInitializer mNoteTaskInitializer;
@Mock DesktopMode mDesktopMode;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mFloatingTasks),
mWMShell = new WMShell(
mContext,
mShellInterface,
Optional.of(mPip),
Optional.of(mSplitScreen),
Optional.of(mOneHanded),
Optional.of(mDesktopMode),
mCommandQueue, mConfigurationController, mKeyguardStateController,
mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer,
mWakefulnessLifecycle, mUserTracker, mSysUiMainExecutor);
mCommandQueue,
mConfigurationController,
mKeyguardStateController,
mKeyguardUpdateMonitor,
mScreenLifecycle,
mSysUiState,
mProtoTracer,
mWakefulnessLifecycle,
mUserTracker,
mNoteTaskInitializer,
mSysUiMainExecutor
);
}
@Test

View File

@@ -4052,6 +4052,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyEvent.KEYCODE_DEMO_APP_2:
case KeyEvent.KEYCODE_DEMO_APP_3:
case KeyEvent.KEYCODE_DEMO_APP_4: {
// TODO(b/254604589): Dispatch KeyEvent to System UI.
sendSystemKeyToStatusBarAsync(keyCode);
// Just drop if keys are not intercepted for direct key.
result &= ~ACTION_PASS_TO_USER;
break;