Add Keyguard Shortcut for Note Task
Allow a user to manually set a Note Task shortcut in the Keyguard screen using the new Quick Affordance UI. The shortcut should only be available when: 1. The device supports Note Task (i.e., tablets). 2. Note Task feature flag is enabled: `adb shell cmd statusbar flag 1900 on` 3. Quick Affordance flag is enabled: `adb shell cmd statusbar flag 216 on` 4. Quick Affordance new UI flag is enabled: `adb shell cmd statusbar flag 222 on` Later, we will add it as a default on the Keyguard to any device running `TitanSysuiConfigOverlay` if and only if a Stylus is detected (i.e., first usage). Test: atest SystemUITests:NoteTaskQuickAffordanceConfigTest Fixes: b/251208173 Change-Id: I2a6251c44946b677d8b8c64c12591e0a084ee965
This commit is contained in:
@@ -907,7 +907,7 @@
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:label="@string/note_task_button_label"
|
||||
android:icon="@drawable/ic_note_task_button">
|
||||
android:icon="@drawable/ic_note_task_shortcut_widget">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.CREATE_SHORTCUT" />
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="@android:color/white"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="@android:color/white"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
|
||||
</vector>
|
||||
@@ -19,10 +19,13 @@
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="#636C6F"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="#636C6F"
|
||||
android:fillType="evenOdd"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
|
||||
</vector>
|
||||
@@ -36,4 +36,15 @@
|
||||
<integer name="qs_security_footer_maxLines">1</integer>
|
||||
|
||||
<bool name="config_use_large_screen_shade_header">true</bool>
|
||||
|
||||
<!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
|
||||
string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
|
||||
separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
|
||||
default is displayed by System UI as long as the user hasn't made a different choice for that
|
||||
slot. If the user did make a choice, even if the choice is the "None" option, the default is
|
||||
ignored. -->
|
||||
<string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
|
||||
<item>bottom_start:home</item>
|
||||
<item>bottom_end:create_note</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
@@ -25,6 +25,7 @@ package com.android.systemui.keyguard.data.quickaffordance
|
||||
object BuiltInKeyguardQuickAffordanceKeys {
|
||||
// Please keep alphabetical order of const names to simplify future maintenance.
|
||||
const val CAMERA = "camera"
|
||||
const val CREATE_NOTE = "create_note"
|
||||
const val DO_NOT_DISTURB = "do_not_disturb"
|
||||
const val FLASHLIGHT = "flashlight"
|
||||
const val HOME_CONTROLS = "home"
|
||||
|
||||
@@ -72,6 +72,9 @@ constructor(
|
||||
companion object {
|
||||
// TODO(b/254606432): 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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,13 @@ package com.android.systemui.notetask
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.KeyguardManager
|
||||
import android.app.role.RoleManager
|
||||
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 com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
|
||||
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
|
||||
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
|
||||
import dagger.Binds
|
||||
@@ -33,20 +35,25 @@ import dagger.multibindings.IntoMap
|
||||
import java.util.Optional
|
||||
|
||||
/** Compose all dependencies required by Note Task feature. */
|
||||
@Module
|
||||
@Module(includes = [NoteTaskQuickAffordanceModule::class])
|
||||
internal interface NoteTaskModule {
|
||||
|
||||
@[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
|
||||
fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity?
|
||||
fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
|
||||
|
||||
@[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
|
||||
fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity?
|
||||
fun CreateNoteTaskShortcutActivity.bindNoteTaskShortcutActivity(): Activity
|
||||
|
||||
companion object {
|
||||
|
||||
@[Provides NoteTaskEnabledKey]
|
||||
fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
|
||||
return featureFlags.isEnabled(Flags.NOTE_TASKS)
|
||||
fun provideIsNoteTaskEnabled(
|
||||
featureFlags: FeatureFlags,
|
||||
roleManager: RoleManager,
|
||||
): Boolean {
|
||||
val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.NOTE_ROLE)
|
||||
val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
|
||||
return isRoleAvailable && isFeatureEnabled
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.quickaffordance
|
||||
|
||||
import android.content.Context
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.Expandable
|
||||
import com.android.systemui.common.shared.model.ContentDescription
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
|
||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
|
||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
|
||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
|
||||
import com.android.systemui.notetask.NoteTaskController
|
||||
import com.android.systemui.notetask.NoteTaskEnabledKey
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
internal class NoteTaskQuickAffordanceConfig
|
||||
@Inject
|
||||
constructor(
|
||||
context: Context,
|
||||
private val noteTaskController: NoteTaskController,
|
||||
@NoteTaskEnabledKey private val isEnabled: Boolean,
|
||||
) : KeyguardQuickAffordanceConfig {
|
||||
|
||||
override val key = BuiltInKeyguardQuickAffordanceKeys.CREATE_NOTE
|
||||
|
||||
override val pickerName: String = context.getString(R.string.note_task_button_label)
|
||||
|
||||
override val pickerIconResourceId = R.drawable.ic_note_task_shortcut_keyguard
|
||||
|
||||
override val lockScreenState = flowOf(getLockScreenState())
|
||||
|
||||
// TODO(b/265949213)
|
||||
private fun getLockScreenState() =
|
||||
if (isEnabled) {
|
||||
val icon = Icon.Resource(pickerIconResourceId, ContentDescription.Loaded(pickerName))
|
||||
LockScreenState.Visible(icon)
|
||||
} else {
|
||||
LockScreenState.Hidden
|
||||
}
|
||||
|
||||
override suspend fun getPickerScreenState() =
|
||||
if (isEnabled) {
|
||||
PickerScreenState.Default()
|
||||
} else {
|
||||
PickerScreenState.UnavailableOnDevice
|
||||
}
|
||||
|
||||
override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
|
||||
noteTaskController.showNoteTask()
|
||||
return OnTriggeredResult.Handled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.quickaffordance
|
||||
|
||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoSet
|
||||
|
||||
@Module
|
||||
internal interface NoteTaskQuickAffordanceModule {
|
||||
|
||||
@[Binds IntoSet]
|
||||
fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig
|
||||
}
|
||||
@@ -46,7 +46,7 @@ internal class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentA
|
||||
id = SHORTCUT_ID,
|
||||
shortLabel = getString(R.string.note_task_button_label),
|
||||
intent = LaunchNoteTaskActivity.newIntent(context = this),
|
||||
iconResource = R.drawable.ic_note_task_button,
|
||||
iconResource = R.drawable.ic_note_task_shortcut_widget,
|
||||
)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package com.android.systemui.notetask.quickaffordance
|
||||
|
||||
import android.test.suitebuilder.annotation.SmallTest
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.common.shared.model.ContentDescription
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
|
||||
import com.android.systemui.notetask.NoteTaskController
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
/**
|
||||
* Tests for [NoteTaskQuickAffordanceConfig].
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* - atest SystemUITests:NoteTaskQuickAffordanceConfigTest
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
|
||||
|
||||
@Mock lateinit var noteTaskController: NoteTaskController
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(noteTaskController.showNoteTask()).then {}
|
||||
}
|
||||
|
||||
private fun createUnderTest(isEnabled: Boolean) =
|
||||
NoteTaskQuickAffordanceConfig(
|
||||
context = context,
|
||||
noteTaskController = noteTaskController,
|
||||
isEnabled = isEnabled,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun lockScreenState_isNotEnabled_shouldEmitHidden() = runTest {
|
||||
val underTest = createUnderTest(isEnabled = false)
|
||||
|
||||
val actual = collectLastValue(underTest.lockScreenState)
|
||||
|
||||
assertThat(actual()).isEqualTo(LockScreenState.Hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lockScreenState_isEnabled_shouldEmitVisible() = runTest {
|
||||
val stringResult = "Notetaking"
|
||||
val underTest = createUnderTest(isEnabled = true)
|
||||
|
||||
val actual = collectLastValue(underTest.lockScreenState)
|
||||
|
||||
val expected =
|
||||
LockScreenState.Visible(
|
||||
icon =
|
||||
Icon.Resource(
|
||||
res = R.drawable.ic_note_task_shortcut_keyguard,
|
||||
contentDescription = ContentDescription.Loaded(stringResult),
|
||||
)
|
||||
)
|
||||
assertThat(actual()).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onTriggered_shouldLaunchNoteTask() {
|
||||
val underTest = createUnderTest(isEnabled = false)
|
||||
|
||||
underTest.onTriggered(expandable = null)
|
||||
|
||||
verify(noteTaskController).showNoteTask()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user