Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ddec7a40 authored by Marcello Galhardo's avatar Marcello Galhardo
Browse files

Update Note Task shortcut when Notes Role is changed

Test: atest SystemUITests:NoteTaskInitializerTest
Test: atest SystemUITests:NoteTaskControllerTest

Fixes: b/261843062

Change-Id: I8bf010024559228797af0867e20da00ce35bc231
parent df60ec39
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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" />
+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
+62 −14
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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>,
@@ -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) {
@@ -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())
}
+6 −3
Original line number Diff line number Diff line
@@ -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

@@ -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

+12 −2
Original line number Diff line number Diff line
@@ -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. */
@@ -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,
) {

@@ -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