summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/AndroidManifest.xml6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt222
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java27
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java3
14 files changed, 785 insertions, 11 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b5145f926abd..4267ba2ff0b7 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -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"/>
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7e31626983e7..e47e636fa445 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -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 = {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 68c18db167c5..e76247c20336 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -328,6 +328,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 ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
new file mode 100644
index 000000000000..d247f249e2fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -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)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
new file mode 100644
index 000000000000..e0bf1da2f652
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
@@ -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
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
new file mode 100644
index 000000000000..d84717da3a21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -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)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
new file mode 100644
index 000000000000..98d69910aac3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
@@ -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
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
new file mode 100644
index 000000000000..035396a6fc76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -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())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 309f1681b964..02738d5ae48b 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
new file mode 100644
index 000000000000..f20c6a29b840
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -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)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
new file mode 100644
index 000000000000..f344c8d9eec4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -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())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
new file mode 100644
index 000000000000..dd2cc2ffc9db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
@@ -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()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 7af66f641837..7ae47b41d5ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -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
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 32ef014bbad7..aec1728ec46a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -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;