Loading packages/SystemUI/AndroidManifest.xml +3 −0 Original line number Diff line number Diff line Loading @@ -235,7 +235,10 @@ <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" /> <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- role holder APIs --> <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" /> <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <!-- It's like, reality, but, you know, virtual --> <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" /> Loading packages/SystemUI/src/com/android/systemui/notetask/InternalNoteTaskApi.kt 0 → 100644 +34 −0 Original line number Diff line number Diff line /* * 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 /** * Marks declarations that are **internal** in note task API, which means that should not be used * outside of `com.android.systemui.notetask`. */ @Retention(value = AnnotationRetention.BINARY) @Target( AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS, AnnotationTarget.PROPERTY ) @RequiresOptIn( level = RequiresOptIn.Level.ERROR, message = "This is an internal API, do not it outside `com.android.systemui.notetask`", ) internal annotation class InternalNoteTaskApi packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +62 −14 Original line number Diff line number Diff line Loading @@ -14,18 +14,21 @@ * limitations under the License. */ @file:OptIn(InternalNoteTaskApi::class) package com.android.systemui.notetask import android.app.KeyguardManager import android.app.admin.DevicePolicyManager import android.app.role.OnRoleHoldersChangedListener import android.app.role.RoleManager import android.app.role.RoleManager.ROLE_NOTES import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.pm.PackageManager import android.content.pm.ShortcutManager import android.os.Build import android.os.UserHandle import android.os.UserManager Loading @@ -33,6 +36,8 @@ import android.util.Log import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity import com.android.systemui.settings.UserTracker import com.android.systemui.util.kotlin.getOrNull Loading @@ -55,6 +60,8 @@ class NoteTaskController @Inject constructor( private val context: Context, private val roleManager: RoleManager, private val shortcutManager: ShortcutManager, private val resolver: NoteTaskInfoResolver, private val eventLogger: NoteTaskEventLogger, private val optionalBubbles: Optional<Bubbles>, Loading Loading @@ -133,7 +140,7 @@ constructor( infoReference.set(info) // TODO(b/266686199): We should handle when app not available. For now, we log. val intent = createNoteIntent(info) val intent = createNoteTaskIntent(info) try { logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" } when (info.launchMode) { Loading Loading @@ -182,30 +189,71 @@ constructor( logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" } } /** * Updates all [NoteTaskController] related information, including but not exclusively the * widget shortcut created by the [user] - by default it will use the current user. * * Keep in mind the shortcut API has a * [rate limiting](https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#rate-limiting) * and may not be updated in real-time. To reduce the chance of stale shortcuts, we run the * function during System UI initialization. */ fun updateNoteTaskAsUser(user: UserHandle) { val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user) val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty() setNoteTaskShortcutEnabled(hasNotesRoleHolder) if (hasNotesRoleHolder) { shortcutManager.enableShortcuts(listOf(SHORTCUT_ID)) val updatedShortcut = roleManager.createNoteShortcutInfoAsUser(context, user) shortcutManager.updateShortcuts(listOf(updatedShortcut)) } else { shortcutManager.disableShortcuts(listOf(SHORTCUT_ID)) } } /** @see OnRoleHoldersChangedListener */ fun onRoleHoldersChanged(roleName: String, user: UserHandle) { if (roleName == ROLE_NOTES) updateNoteTaskAsUser(user) } companion object { val TAG = NoteTaskController::class.simpleName.orEmpty() const val SHORTCUT_ID = "note_task_shortcut_id" /** * Shortcut extra which can point to a package name and can be used to indicate an alternate * badge info. Launcher only reads this if the shortcut comes from a system app. * * Duplicated from [com.android.launcher3.icons.IconCache]. * * @see com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE */ const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package" } } private fun createNoteIntent(info: NoteTaskInfo): Intent = /** Creates an [Intent] for [ROLE_NOTES]. */ private fun createNoteTaskIntent(info: NoteTaskInfo): Intent = Intent(Intent.ACTION_CREATE_NOTE).apply { setPackage(info.packageName) // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint // was used to start it. // was used to start the note task. putExtra(Intent.EXTRA_USE_STYLUS_MODE, true) addFlags(FLAG_ACTIVITY_NEW_TASK) // We should ensure the note experience can be open both as a full screen (lock screen) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) // We should ensure the note experience can be opened both as a full screen (lockscreen) // and inside the app bubble (contextual). These additional flags will do that. if (info.launchMode == NoteTaskLaunchMode.Activity) { addFlags(FLAG_ACTIVITY_MULTIPLE_TASK) addFlags(FLAG_ACTIVITY_NEW_DOCUMENT) addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) } } private inline fun logDebug(message: () -> String) { if (Build.IS_DEBUGGABLE) { Log.d(NoteTaskController.TAG, message()) } /** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */ private inline fun Any.logDebug(message: () -> String) { if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message()) } packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt +6 −3 Original line number Diff line number Diff line Loading @@ -14,13 +14,17 @@ * limitations under the License. */ @file:OptIn(InternalNoteTaskApi::class) package com.android.systemui.notetask import android.app.role.RoleManager import android.app.role.RoleManager.ROLE_NOTES import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.os.UserHandle import android.util.Log import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser import com.android.systemui.settings.UserTracker import javax.inject.Inject Loading @@ -36,10 +40,9 @@ constructor( entryPoint: NoteTaskEntryPoint? = null, isKeyguardLocked: Boolean = false, ): NoteTaskInfo? { // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking. val user = userTracker.userHandle val packageName = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user).firstOrNull() val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user) if (packageName.isNullOrEmpty()) return null Loading packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +12 −2 Original line number Diff line number Diff line Loading @@ -15,11 +15,15 @@ */ package com.android.systemui.notetask import android.app.role.RoleManager import android.os.UserHandle import android.view.KeyEvent import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.CommandQueue import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import java.util.concurrent.Executor import javax.inject.Inject /** Class responsible to "glue" all note task dependencies. */ Loading @@ -27,8 +31,10 @@ internal class NoteTaskInitializer @Inject constructor( private val controller: NoteTaskController, private val roleManager: RoleManager, private val commandQueue: CommandQueue, private val optionalBubbles: Optional<Bubbles>, @Background private val backgroundExecutor: Executor, @NoteTaskEnabledKey private val isEnabled: Boolean, ) { Loading @@ -43,11 +49,15 @@ constructor( } fun initialize() { controller.setNoteTaskShortcutEnabled(isEnabled) // Guard against feature not being enabled or mandatory dependencies aren't available. if (!isEnabled || optionalBubbles.isEmpty) return controller.setNoteTaskShortcutEnabled(true) commandQueue.addCallback(callbacks) roleManager.addOnRoleHoldersChangedListenerAsUser( backgroundExecutor, controller::onRoleHoldersChanged, UserHandle.ALL, ) } } Loading
packages/SystemUI/AndroidManifest.xml +3 −0 Original line number Diff line number Diff line Loading @@ -235,7 +235,10 @@ <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" /> <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- role holder APIs --> <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" /> <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <!-- It's like, reality, but, you know, virtual --> <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" /> Loading
packages/SystemUI/src/com/android/systemui/notetask/InternalNoteTaskApi.kt 0 → 100644 +34 −0 Original line number Diff line number Diff line /* * 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 /** * Marks declarations that are **internal** in note task API, which means that should not be used * outside of `com.android.systemui.notetask`. */ @Retention(value = AnnotationRetention.BINARY) @Target( AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS, AnnotationTarget.PROPERTY ) @RequiresOptIn( level = RequiresOptIn.Level.ERROR, message = "This is an internal API, do not it outside `com.android.systemui.notetask`", ) internal annotation class InternalNoteTaskApi
packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +62 −14 Original line number Diff line number Diff line Loading @@ -14,18 +14,21 @@ * limitations under the License. */ @file:OptIn(InternalNoteTaskApi::class) package com.android.systemui.notetask import android.app.KeyguardManager import android.app.admin.DevicePolicyManager import android.app.role.OnRoleHoldersChangedListener import android.app.role.RoleManager import android.app.role.RoleManager.ROLE_NOTES import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.pm.PackageManager import android.content.pm.ShortcutManager import android.os.Build import android.os.UserHandle import android.os.UserManager Loading @@ -33,6 +36,8 @@ import android.util.Log import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity import com.android.systemui.settings.UserTracker import com.android.systemui.util.kotlin.getOrNull Loading @@ -55,6 +60,8 @@ class NoteTaskController @Inject constructor( private val context: Context, private val roleManager: RoleManager, private val shortcutManager: ShortcutManager, private val resolver: NoteTaskInfoResolver, private val eventLogger: NoteTaskEventLogger, private val optionalBubbles: Optional<Bubbles>, Loading Loading @@ -133,7 +140,7 @@ constructor( infoReference.set(info) // TODO(b/266686199): We should handle when app not available. For now, we log. val intent = createNoteIntent(info) val intent = createNoteTaskIntent(info) try { logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" } when (info.launchMode) { Loading Loading @@ -182,30 +189,71 @@ constructor( logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" } } /** * Updates all [NoteTaskController] related information, including but not exclusively the * widget shortcut created by the [user] - by default it will use the current user. * * Keep in mind the shortcut API has a * [rate limiting](https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#rate-limiting) * and may not be updated in real-time. To reduce the chance of stale shortcuts, we run the * function during System UI initialization. */ fun updateNoteTaskAsUser(user: UserHandle) { val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user) val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty() setNoteTaskShortcutEnabled(hasNotesRoleHolder) if (hasNotesRoleHolder) { shortcutManager.enableShortcuts(listOf(SHORTCUT_ID)) val updatedShortcut = roleManager.createNoteShortcutInfoAsUser(context, user) shortcutManager.updateShortcuts(listOf(updatedShortcut)) } else { shortcutManager.disableShortcuts(listOf(SHORTCUT_ID)) } } /** @see OnRoleHoldersChangedListener */ fun onRoleHoldersChanged(roleName: String, user: UserHandle) { if (roleName == ROLE_NOTES) updateNoteTaskAsUser(user) } companion object { val TAG = NoteTaskController::class.simpleName.orEmpty() const val SHORTCUT_ID = "note_task_shortcut_id" /** * Shortcut extra which can point to a package name and can be used to indicate an alternate * badge info. Launcher only reads this if the shortcut comes from a system app. * * Duplicated from [com.android.launcher3.icons.IconCache]. * * @see com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE */ const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package" } } private fun createNoteIntent(info: NoteTaskInfo): Intent = /** Creates an [Intent] for [ROLE_NOTES]. */ private fun createNoteTaskIntent(info: NoteTaskInfo): Intent = Intent(Intent.ACTION_CREATE_NOTE).apply { setPackage(info.packageName) // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint // was used to start it. // was used to start the note task. putExtra(Intent.EXTRA_USE_STYLUS_MODE, true) addFlags(FLAG_ACTIVITY_NEW_TASK) // We should ensure the note experience can be open both as a full screen (lock screen) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) // We should ensure the note experience can be opened both as a full screen (lockscreen) // and inside the app bubble (contextual). These additional flags will do that. if (info.launchMode == NoteTaskLaunchMode.Activity) { addFlags(FLAG_ACTIVITY_MULTIPLE_TASK) addFlags(FLAG_ACTIVITY_NEW_DOCUMENT) addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) } } private inline fun logDebug(message: () -> String) { if (Build.IS_DEBUGGABLE) { Log.d(NoteTaskController.TAG, message()) } /** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */ private inline fun Any.logDebug(message: () -> String) { if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message()) }
packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt +6 −3 Original line number Diff line number Diff line Loading @@ -14,13 +14,17 @@ * limitations under the License. */ @file:OptIn(InternalNoteTaskApi::class) package com.android.systemui.notetask import android.app.role.RoleManager import android.app.role.RoleManager.ROLE_NOTES import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.os.UserHandle import android.util.Log import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser import com.android.systemui.settings.UserTracker import javax.inject.Inject Loading @@ -36,10 +40,9 @@ constructor( entryPoint: NoteTaskEntryPoint? = null, isKeyguardLocked: Boolean = false, ): NoteTaskInfo? { // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking. val user = userTracker.userHandle val packageName = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user).firstOrNull() val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user) if (packageName.isNullOrEmpty()) return null Loading
packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +12 −2 Original line number Diff line number Diff line Loading @@ -15,11 +15,15 @@ */ package com.android.systemui.notetask import android.app.role.RoleManager import android.os.UserHandle import android.view.KeyEvent import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.CommandQueue import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import java.util.concurrent.Executor import javax.inject.Inject /** Class responsible to "glue" all note task dependencies. */ Loading @@ -27,8 +31,10 @@ internal class NoteTaskInitializer @Inject constructor( private val controller: NoteTaskController, private val roleManager: RoleManager, private val commandQueue: CommandQueue, private val optionalBubbles: Optional<Bubbles>, @Background private val backgroundExecutor: Executor, @NoteTaskEnabledKey private val isEnabled: Boolean, ) { Loading @@ -43,11 +49,15 @@ constructor( } fun initialize() { controller.setNoteTaskShortcutEnabled(isEnabled) // Guard against feature not being enabled or mandatory dependencies aren't available. if (!isEnabled || optionalBubbles.isEmpty) return controller.setNoteTaskShortcutEnabled(true) commandQueue.addCallback(callbacks) roleManager.addOnRoleHoldersChangedListenerAsUser( backgroundExecutor, controller::onRoleHoldersChanged, UserHandle.ALL, ) } }