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

Commit 4c62c450 authored by Marcello Galhardo's avatar Marcello Galhardo Committed by Automerger Merge Worker
Browse files

Merge "Add monitoring to note taking experience" into udc-dev am: b7bdaebc am: 377669b7

parents 57489b2e 377669b7
Loading
Loading
Loading
Loading
+75 −47
Original line number Original line Diff line number Diff line
@@ -22,15 +22,18 @@ import android.content.ComponentName
import android.content.Context
import android.content.Context
import android.content.Intent
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager
import android.os.Build
import android.os.UserManager
import android.os.UserManager
import android.util.Log
import android.util.Log
import com.android.internal.logging.UiEvent
import androidx.annotation.VisibleForTesting
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.Bubbles
import com.android.wm.shell.bubbles.Bubbles
import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
import java.util.Optional
import java.util.Optional
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
import javax.inject.Inject


/**
/**
@@ -41,18 +44,40 @@ import javax.inject.Inject
 * Currently, we only support a single task per time.
 * Currently, we only support a single task per time.
 */
 */
@SysUISingleton
@SysUISingleton
internal class NoteTaskController
class NoteTaskController
@Inject
@Inject
constructor(
constructor(
    private val context: Context,
    private val context: Context,
    private val resolver: NoteTaskInfoResolver,
    private val resolver: NoteTaskInfoResolver,
    private val eventLogger: NoteTaskEventLogger,
    private val optionalBubbles: Optional<Bubbles>,
    private val optionalBubbles: Optional<Bubbles>,
    private val optionalKeyguardManager: Optional<KeyguardManager>,
    private val optionalUserManager: Optional<UserManager>,
    private val optionalUserManager: Optional<UserManager>,
    private val optionalKeyguardManager: Optional<KeyguardManager>,
    @NoteTaskEnabledKey private val isEnabled: Boolean,
    @NoteTaskEnabledKey private val isEnabled: Boolean,
    private val uiEventLogger: UiEventLogger,
) {
) {


    @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>()

    /** @see BubbleExpandListener */
    fun onBubbleExpandChanged(isExpanding: Boolean, key: String?) {
        if (!isEnabled) return

        if (key != Bubble.KEY_APP_BUBBLE) return

        val info = infoReference.getAndSet(null)

        // Safe guard mechanism, this callback should only be called for app bubbles.
        if (info?.launchMode != NoteTaskLaunchMode.AppBubble) return

        if (isExpanding) {
            logDebug { "onBubbleExpandChanged - expanding: $info" }
            eventLogger.logNoteTaskOpened(info)
        } else {
            logDebug { "onBubbleExpandChanged - collapsing: $info" }
            eventLogger.logNoteTaskClosed(info)
        }
    }

    /**
    /**
     * Shows a note task. How the task is shown will depend on when the method is invoked.
     * Shows a note task. How the task is shown will depend on when the method is invoked.
     *
     *
@@ -69,32 +94,50 @@ constructor(
     * That will let users open other apps in full screen, and take contextual notes.
     * That will let users open other apps in full screen, and take contextual notes.
     */
     */
    @JvmOverloads
    @JvmOverloads
    fun showNoteTask(isInMultiWindowMode: Boolean = false, uiEvent: ShowNoteTaskUiEvent? = null) {
    fun showNoteTask(

        entryPoint: NoteTaskEntryPoint,
        isInMultiWindowMode: Boolean = false,
    ) {
        if (!isEnabled) return
        if (!isEnabled) return


        val bubbles = optionalBubbles.getOrNull() ?: return
        val bubbles = optionalBubbles.getOrNull() ?: return
        val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
        val userManager = optionalUserManager.getOrNull() ?: return
        val userManager = optionalUserManager.getOrNull() ?: return
        val keyguardManager = optionalKeyguardManager.getOrNull() ?: return


        // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
        // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
        if (!userManager.isUserUnlocked) return
        if (!userManager.isUserUnlocked) return


        val noteTaskInfo = resolver.resolveInfo() ?: return
        val info =
            resolver.resolveInfo(
                entryPoint = entryPoint,
                isInMultiWindowMode = isInMultiWindowMode,
                isKeyguardLocked = keyguardManager.isKeyguardLocked,
            )
                ?: return


        uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) }
        infoReference.set(info)


        // TODO(b/266686199): We should handle when app not available. For now, we log.
        // TODO(b/266686199): We should handle when app not available. For now, we log.
        val intent = noteTaskInfo.toCreateNoteIntent()
        val intent = createNoteIntent(info)
        try {
        try {
            if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
            logDebug { "onShowNoteTask - start: $info" }
                context.startActivity(intent)
            when (info.launchMode) {
            } else {
                is NoteTaskLaunchMode.AppBubble -> {
                    bubbles.showOrHideAppBubble(intent)
                    bubbles.showOrHideAppBubble(intent)
                    // App bubble logging happens on `onBubbleExpandChanged`.
                    logDebug { "onShowNoteTask - opened as app bubble: $info" }
                }
                }
                is NoteTaskLaunchMode.Activity -> {
                    context.startActivity(intent)
                    eventLogger.logNoteTaskOpened(info)
                    logDebug { "onShowNoteTask - opened as activity: $info" }
                }
            }
            logDebug { "onShowNoteTask - success: $info" }
        } catch (e: ActivityNotFoundException) {
        } catch (e: ActivityNotFoundException) {
            Log.e(TAG, "Activity not found for action: $ACTION_CREATE_NOTE.", e)
            logDebug { "onShowNoteTask - failed: $info" }
        }
        }
        logDebug { "onShowNoteTask - compoleted: $info" }
    }
    }


    /**
    /**
@@ -119,41 +162,12 @@ constructor(
            enabledState,
            enabledState,
            PackageManager.DONT_KILL_APP,
            PackageManager.DONT_KILL_APP,
        )
        )
    }

    /** IDs of UI events accepted by [showNoteTask]. */
    enum class ShowNoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
        @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.")
        NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294),

        /* ktlint-disable max-line-length */
        @UiEvent(
            doc =
                "User opened a note by pressing the stylus tail button while the screen was unlocked."
        )
        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295),
        @UiEvent(
            doc =
                "User opened a note by pressing the stylus tail button while the screen was locked."
        )
        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296),
        @UiEvent(doc = "User opened a note by tapping on an app shortcut.")
        NOTE_OPENED_VIA_SHORTCUT(1297);


        override fun getId() = _id
        logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
    }
    }


    companion object {
    companion object {
        private val TAG = NoteTaskController::class.simpleName.orEmpty()
        val TAG = NoteTaskController::class.simpleName.orEmpty()

        private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent {
            return Intent(ACTION_CREATE_NOTE)
                .setPackage(packageName)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
                // was used to start it.
                .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
        }


        // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
        // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
        const val NOTE_TASK_KEY_EVENT = 311
        const val NOTE_TASK_KEY_EVENT = 311
@@ -165,3 +179,17 @@ constructor(
        const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
        const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
    }
    }
}
}

private fun createNoteIntent(info: NoteTaskInfo): Intent =
    Intent(NoteTaskController.ACTION_CREATE_NOTE)
        .setPackage(info.packageName)
        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
        // was used to start it.
        .putExtra(NoteTaskController.INTENT_EXTRA_USE_STYLUS_MODE, true)

private inline fun logDebug(message: () -> String) {
    if (Build.IS_DEBUGGABLE) {
        Log.d(NoteTaskController.TAG, message())
    }
}
+1 −1
Original line number Original line Diff line number Diff line
@@ -19,4 +19,4 @@ package com.android.systemui.notetask
import javax.inject.Qualifier
import javax.inject.Qualifier


/** Key associated with a [Boolean] flag that enables or disables the note task feature. */
/** Key associated with a [Boolean] flag that enables or disables the note task feature. */
@Qualifier internal annotation class NoteTaskEnabledKey
@Qualifier annotation class NoteTaskEnabledKey
+41 −0
Original line number Original line 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

import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceConfig
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
import com.android.systemui.screenshot.AppClipsTrampolineActivity

/**
 * Supported entry points for [NoteTaskController.showNoteTask].
 *
 * An entry point represents where the note task has ben called from. In rare cases, it may
 * represent a "re-entry" (i.e., [APP_CLIPS]).
 */
enum class NoteTaskEntryPoint {

    /** @see [LaunchNoteTaskActivity] */
    WIDGET_PICKER_SHORTCUT,

    /** @see [NoteTaskQuickAffordanceConfig] */
    QUICK_AFFORDANCE,

    /** @see [NoteTaskInitializer.callbacks] */
    TAIL_BUTTON,

    /** @see [AppClipsTrampolineActivity] */
    APP_CLIPS,
}
+101 −0
Original line number Original line 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

import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT
import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
import javax.inject.Inject

/**
 * A wrapper around [UiEventLogger] specialized in the note taking UI events.
 *
 * if the accepted [NoteTaskInfo] contains a [NoteTaskInfo.entryPoint], it will be logged as the
 * correct [NoteTaskUiEvent]. If null, it will be ignored.
 *
 * @see NoteTaskController for usage examples.
 */
class NoteTaskEventLogger @Inject constructor(private val uiEventLogger: UiEventLogger) {

    /** Logs a [NoteTaskInfo] as an **open** [NoteTaskUiEvent], including package name and uid. */
    fun logNoteTaskOpened(info: NoteTaskInfo) {
        val event =
            when (info.entryPoint) {
                TAIL_BUTTON -> {
                    if (info.isKeyguardLocked) {
                        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
                    } else {
                        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
                    }
                }
                WIDGET_PICKER_SHORTCUT -> NOTE_OPENED_VIA_SHORTCUT
                QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
                APP_CLIPS -> return
                null -> return
            }
        uiEventLogger.log(event, info.uid, info.packageName)
    }

    /** Logs a [NoteTaskInfo] as a **closed** [NoteTaskUiEvent], including package name and uid. */
    fun logNoteTaskClosed(info: NoteTaskInfo) {
        val event =
            when (info.entryPoint) {
                TAIL_BUTTON -> {
                    if (info.isKeyguardLocked) {
                        NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
                    } else {
                        NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
                    }
                }
                WIDGET_PICKER_SHORTCUT -> return
                QUICK_AFFORDANCE -> return
                APP_CLIPS -> return
                null -> return
            }
        uiEventLogger.log(event, info.uid, info.packageName)
    }

    /** IDs of UI events accepted by [NoteTaskController]. */
    enum class NoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {

        @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.")
        NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294),

        @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was unlocked.") // ktlint-disable max-line-length
        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295),

        @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was locked.") // ktlint-disable max-line-length
        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296),

        @UiEvent(doc = "User opened a note by tapping on an app shortcut.")
        NOTE_OPENED_VIA_SHORTCUT(1297),

        @UiEvent(doc = "Note closed via a tail button while device is unlocked")
        NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON(1311),

        @UiEvent(doc = "Note closed via a tail button while device is locked")
        NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1312);

        override fun getId() = _id
    }
}
+33 −0
Original line number Original line 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

/** Contextual information required to launch a Note Task by [NoteTaskController]. */
data class NoteTaskInfo(
    val packageName: String,
    val uid: Int,
    val entryPoint: NoteTaskEntryPoint? = null,
    val isInMultiWindowMode: Boolean = false,
    val isKeyguardLocked: Boolean = false,
) {

    val launchMode: NoteTaskLaunchMode =
        if (isInMultiWindowMode || isKeyguardLocked) {
            NoteTaskLaunchMode.Activity
        } else {
            NoteTaskLaunchMode.AppBubble
        }
}
Loading