diff options
| author | 2023-02-27 10:23:18 +0000 | |
|---|---|---|
| committer | 2023-02-27 10:23:18 +0000 | |
| commit | b7bdaebc9216936ff0065fbff4b028f9efe5a35e (patch) | |
| tree | 8b626194d541040fbe4fc659daef39e0316458ef | |
| parent | c8d683a04029e4cbd0ba678263086bdaa46895ff (diff) | |
| parent | ca626a8d7477d0d02c5e3b9c249cbe9c0b57ffd9 (diff) | |
Merge "Add monitoring to note taking experience" into udc-dev
21 files changed, 768 insertions, 244 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index be615d63a3d7..f7b7db4e366f 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -22,15 +22,18 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.os.Build import android.os.UserManager import android.util.Log -import com.android.internal.logging.UiEvent -import com.android.internal.logging.UiEventLogger +import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity 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.BubbleExpandListener import java.util.Optional +import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject /** @@ -41,18 +44,40 @@ import javax.inject.Inject * Currently, we only support a single task per time. */ @SysUISingleton -internal class NoteTaskController +class NoteTaskController @Inject constructor( private val context: Context, private val resolver: NoteTaskInfoResolver, + private val eventLogger: NoteTaskEventLogger, private val optionalBubbles: Optional<Bubbles>, - private val optionalKeyguardManager: Optional<KeyguardManager>, private val optionalUserManager: Optional<UserManager>, + private val optionalKeyguardManager: Optional<KeyguardManager>, @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. * @@ -69,32 +94,50 @@ constructor( * That will let users open other apps in full screen, and take contextual notes. */ @JvmOverloads - fun showNoteTask(isInMultiWindowMode: Boolean = false, uiEvent: ShowNoteTaskUiEvent? = null) { - + fun showNoteTask( + entryPoint: NoteTaskEntryPoint, + isInMultiWindowMode: Boolean = false, + ) { if (!isEnabled) return val bubbles = optionalBubbles.getOrNull() ?: return - val keyguardManager = optionalKeyguardManager.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. 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. - val intent = noteTaskInfo.toCreateNoteIntent() + val intent = createNoteIntent(info) try { - if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) { - context.startActivity(intent) - } else { - bubbles.showOrHideAppBubble(intent) + logDebug { "onShowNoteTask - start: $info" } + when (info.launchMode) { + is NoteTaskLaunchMode.AppBubble -> { + 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) { - 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, 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 { - private 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) - } + val TAG = NoteTaskController::class.simpleName.orEmpty() // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead. 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" } } + +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()) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt index e0bf1da2f652..a2563919955a 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt @@ -19,4 +19,4 @@ 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 +@Qualifier annotation class NoteTaskEnabledKey diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt new file mode 100644 index 000000000000..acc537a8eb36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt @@ -0,0 +1,41 @@ +/* + * 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, +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt new file mode 100644 index 000000000000..16dd16ee137e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt @@ -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. + */ +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 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt new file mode 100644 index 000000000000..28d76474efba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt @@ -0,0 +1,33 @@ +/* + * 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 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt index bd822d40b950..b5d757c6c287 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt @@ -19,51 +19,56 @@ package com.android.systemui.notetask import android.app.role.RoleManager import android.content.Context import android.content.pm.PackageManager +import android.content.pm.PackageManager.ApplicationInfoFlags import android.os.UserHandle import android.util.Log import javax.inject.Inject -internal class NoteTaskInfoResolver +class NoteTaskInfoResolver @Inject constructor( private val context: Context, private val roleManager: RoleManager, private val packageManager: PackageManager, ) { - fun resolveInfo(): NoteTaskInfo? { + fun resolveInfo( + entryPoint: NoteTaskEntryPoint? = null, + isInMultiWindowMode: Boolean = false, + isKeyguardLocked: Boolean = false, + ): NoteTaskInfo? { // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking. val user = context.user val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, user).firstOrNull() if (packageName.isNullOrEmpty()) return null - return NoteTaskInfo(packageName, packageManager.getUidOf(packageName, user)) + return NoteTaskInfo( + packageName = packageName, + uid = packageManager.getUidOf(packageName, user), + entryPoint = entryPoint, + isInMultiWindowMode = isInMultiWindowMode, + isKeyguardLocked = isKeyguardLocked, + ) } - /** Package name and kernel user-ID of a note-taking app. */ - data class NoteTaskInfo(val packageName: String, val uid: Int) - companion object { private val TAG = NoteTaskInfoResolver::class.simpleName.orEmpty() - private val EMPTY_APPLICATION_INFO_FLAGS = PackageManager.ApplicationInfoFlags.of(0)!! + // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead. + const val ROLE_NOTES = "android.app.role.NOTES" + + private val EMPTY_APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0)!! /** * Returns the kernel user-ID of [packageName] for a [user]. Returns zero if the app cannot * be found. */ - private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int { - val applicationInfo = - try { - getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user) - } catch (e: PackageManager.NameNotFoundException) { - Log.e(TAG, "Couldn't find notes app UID", e) - return 0 - } - return applicationInfo.uid - } - - // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead. - const val ROLE_NOTES = "android.app.role.NOTES" + private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int = + try { + getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user).uid + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Couldn't find notes app UID", e) + 0 + } } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index d40bf2b49975..3f4f8d538bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.systemui.notetask -import android.app.KeyguardManager import androidx.annotation.VisibleForTesting import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.util.kotlin.getOrNull import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import javax.inject.Inject @@ -28,11 +25,10 @@ import javax.inject.Inject internal class NoteTaskInitializer @Inject constructor( - private val optionalBubbles: Optional<Bubbles>, - private val noteTaskController: NoteTaskController, + private val controller: NoteTaskController, private val commandQueue: CommandQueue, + private val optionalBubbles: Optional<Bubbles>, @NoteTaskEnabledKey private val isEnabled: Boolean, - private val optionalKeyguardManager: Optional<KeyguardManager>, ) { @VisibleForTesting @@ -40,29 +36,17 @@ constructor( object : CommandQueue.Callbacks { override fun handleSystemKey(keyCode: Int) { if (keyCode == NoteTaskController.NOTE_TASK_KEY_EVENT) { - showNoteTask() + controller.showNoteTask(NoteTaskEntryPoint.TAIL_BUTTON) } } } - private fun showNoteTask() { - val uiEvent = - if (optionalKeyguardManager.isKeyguardLocked) { - NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED - } else { - NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON - } - noteTaskController.showNoteTask(uiEvent = uiEvent) - } - fun initialize() { - if (isEnabled && optionalBubbles.isPresent) { - commandQueue.addCallback(callbacks) - } - noteTaskController.setNoteTaskShortcutEnabled(isEnabled) + controller.setNoteTaskShortcutEnabled(isEnabled) + + // Guard against feature not being enabled or mandatory dependencies aren't available. + if (!isEnabled || optionalBubbles.isEmpty) return + + commandQueue.addCallback(callbacks) } } - -private val Optional<KeyguardManager>.isKeyguardLocked: Boolean - // If there's no KeyguardManager, assume that the keyguard is not locked. - get() = getOrNull()?.isKeyguardLocked ?: false diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt new file mode 100644 index 000000000000..836e103f4d69 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt @@ -0,0 +1,33 @@ +/* + * 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 android.content.Context +import com.android.wm.shell.bubbles.Bubbles + +/** + * Supported ways for launching a note taking experience. + * + * @see [NoteTaskController.showNoteTask] + */ +sealed class NoteTaskLaunchMode { + + /** @see Bubbles.showOrHideAppBubble */ + object AppBubble : NoteTaskLaunchMode() + + /** @see Context.startActivity */ + object Activity : NoteTaskLaunchMode() +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt index b8800a242d06..f16110d7cf5b 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt @@ -36,7 +36,7 @@ import java.util.Optional /** Compose all dependencies required by Note Task feature. */ @Module(includes = [NoteTaskQuickAffordanceModule::class]) -internal interface NoteTaskModule { +interface NoteTaskModule { @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)] fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt index 43869ccda2b1..30660c492baa 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt @@ -27,12 +27,12 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc 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.NoteTaskController.ShowNoteTaskUiEvent import com.android.systemui.notetask.NoteTaskEnabledKey +import com.android.systemui.notetask.NoteTaskEntryPoint import javax.inject.Inject import kotlinx.coroutines.flow.flowOf -internal class NoteTaskQuickAffordanceConfig +class NoteTaskQuickAffordanceConfig @Inject constructor( context: Context, @@ -66,7 +66,7 @@ constructor( override fun onTriggered(expandable: Expandable?): OnTriggeredResult { noteTaskController.showNoteTask( - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE + entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE, ) return OnTriggeredResult.Handled } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt index 7cb932aa1916..2d63dbcb82fa 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt @@ -22,7 +22,7 @@ import dagger.Module import dagger.multibindings.IntoSet @Module -internal interface NoteTaskQuickAffordanceModule { +interface NoteTaskQuickAffordanceModule { @[Binds IntoSet] fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt index 6ab0da6fe3b3..0a1d0089735f 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt @@ -36,7 +36,7 @@ import javax.inject.Inject * href="https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts#custom-pinned">Creating * a custom shortcut activity</a> */ -internal class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() { +class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt index 3ac5bfa09aaa..2b84bf8b4e2e 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt @@ -21,11 +21,11 @@ import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import com.android.systemui.notetask.NoteTaskController -import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent +import com.android.systemui.notetask.NoteTaskEntryPoint import javax.inject.Inject /** Activity responsible for launching the note experience, and finish. */ -internal class LaunchNoteTaskActivity +class LaunchNoteTaskActivity @Inject constructor( private val noteTaskController: NoteTaskController, @@ -35,8 +35,8 @@ constructor( super.onCreate(savedInstanceState) noteTaskController.showNoteTask( + entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT, isInMultiWindowMode = isInMultiWindowMode, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, ) finish() diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java index 1946b8eaee5b..eda38e45c98a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java @@ -50,6 +50,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.notetask.NoteTaskController; +import com.android.systemui.notetask.NoteTaskEntryPoint; import com.android.systemui.settings.UserTracker; import com.android.wm.shell.bubbles.Bubbles; @@ -239,9 +240,8 @@ public class AppClipsTrampolineActivity extends Activity { // Broadcast no longer required, setting it to null. mKillAppClipsBroadcastIntent = null; - // Expand the note bubble before returning the result. As App Clips API is only - // available when in a bubble, isInMultiWindowMode is always false below. - mNoteTaskController.showNoteTask(false); + // Expand the note bubble before returning the result. + mNoteTaskController.showNoteTask(NoteTaskEntryPoint.APP_CLIPS); setResult(RESULT_OK, convertedData); finish(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 17fb05547e00..9ffc35dee2f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -164,6 +164,7 @@ import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder; import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; +import com.android.systemui.notetask.NoteTaskController; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.OverlayPlugin; @@ -642,7 +643,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private NotificationActivityStarter mNotificationActivityStarter; private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy; private final Optional<Bubbles> mBubblesOptional; - private final Bubbles.BubbleExpandListener mBubbleExpandListener; + private final Lazy<NoteTaskController> mNoteTaskControllerLazy; private final Optional<StartingSurface> mStartingSurfaceOptional; private final ActivityIntentHelper mActivityIntentHelper; @@ -705,6 +706,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { WakefulnessLifecycle wakefulnessLifecycle, SysuiStatusBarStateController statusBarStateController, Optional<Bubbles> bubblesOptional, + Lazy<NoteTaskController> noteTaskControllerLazy, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, AccessibilityFloatingMenuController accessibilityFloatingMenuController, @@ -795,6 +797,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mWakefulnessLifecycle = wakefulnessLifecycle; mStatusBarStateController = statusBarStateController; mBubblesOptional = bubblesOptional; + mNoteTaskControllerLazy = noteTaskControllerLazy; mDeviceProvisionedController = deviceProvisionedController; mNavigationBarController = navigationBarController; mAccessibilityFloatingMenuController = accessibilityFloatingMenuController; @@ -852,9 +855,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged); mShadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); - mBubbleExpandListener = (isExpanding, key) -> - mContext.getMainExecutor().execute(this::updateScrimController); - mActivityIntentHelper = new ActivityIntentHelper(mContext); mActivityLaunchAnimator = activityLaunchAnimator; @@ -884,14 +884,21 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } + private void initBubbles(Bubbles bubbles) { + final Bubbles.BubbleExpandListener listener = (isExpanding, key) -> + mContext.getMainExecutor().execute(() -> { + updateScrimController(); + mNoteTaskControllerLazy.get().onBubbleExpandChanged(isExpanding, key); + }); + bubbles.setExpandListener(listener); + } + @Override public void start() { mScreenLifecycle.addObserver(mScreenObserver); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); mUiModeManager = mContext.getSystemService(UiModeManager.class); - if (mBubblesOptional.isPresent()) { - mBubblesOptional.get().setExpandListener(mBubbleExpandListener); - } + mBubblesOptional.ifPresent(this::initBubbles); // Do not restart System UI when the bugreport flag changes. mFeatureFlags.addListener(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, event -> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 39c4e06ff0bb..0f0b7d7579c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -23,16 +23,15 @@ import android.content.pm.PackageManager import android.os.UserManager import android.test.suitebuilder.annotation.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskController.Companion.INTENT_EXTRA_USE_STYLUS_MODE -import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent -import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever +import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.Bubbles import com.google.common.truth.Truth.assertThat import java.util.Optional @@ -44,12 +43,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations -/** - * Tests for [NoteTaskController]. - * - * Build/Install/Run: - * - atest SystemUITests:NoteTaskControllerTest - */ +/** atest SystemUITests:NoteTaskControllerTest */ @SmallTest @RunWith(AndroidJUnit4::class) internal class NoteTaskControllerTest : SysuiTestCase() { @@ -58,97 +52,193 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Mock lateinit var packageManager: PackageManager @Mock lateinit var resolver: NoteTaskInfoResolver @Mock lateinit var bubbles: Bubbles - @Mock lateinit var optionalBubbles: Optional<Bubbles> @Mock lateinit var keyguardManager: KeyguardManager - @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager> - @Mock lateinit var optionalUserManager: Optional<UserManager> @Mock lateinit var userManager: UserManager - @Mock lateinit var uiEventLogger: UiEventLogger + @Mock lateinit var eventLogger: NoteTaskEventLogger + + private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID) @Before fun setUp() { MockitoAnnotations.initMocks(this) whenever(context.packageManager).thenReturn(packageManager) - whenever(resolver.resolveInfo()).thenReturn(NoteTaskInfo(NOTES_PACKAGE_NAME, NOTES_UID)) - whenever(optionalBubbles.orElse(null)).thenReturn(bubbles) - whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager) - whenever(optionalUserManager.orElse(null)).thenReturn(userManager) + whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(noteTaskInfo) whenever(userManager.isUserUnlocked).thenReturn(true) } - private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController { - return NoteTaskController( + private fun createNoteTaskController( + isEnabled: Boolean = true, + bubbles: Bubbles? = this.bubbles, + keyguardManager: KeyguardManager? = this.keyguardManager, + userManager: UserManager? = this.userManager, + ): NoteTaskController = + NoteTaskController( context = context, resolver = resolver, - optionalBubbles = optionalBubbles, - optionalKeyguardManager = optionalKeyguardManager, - optionalUserManager = optionalUserManager, + eventLogger = eventLogger, + optionalBubbles = Optional.ofNullable(bubbles), + optionalUserManager = Optional.ofNullable(userManager), + optionalKeyguardManager = Optional.ofNullable(keyguardManager), isEnabled = isEnabled, - uiEventLogger = uiEventLogger, ) + + // region onBubbleExpandChanged + @Test + fun onBubbleExpandChanged_expanding_logNoteTaskOpened() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = false) + + createNoteTaskController() + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = true, + key = Bubble.KEY_APP_BUBBLE, + ) + + verify(eventLogger).logNoteTaskOpened(expectedInfo) + verifyZeroInteractions(context, bubbles, keyguardManager, userManager) } - // region showNoteTask @Test - fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() { - whenever(keyguardManager.isKeyguardLocked).thenReturn(true) + fun onBubbleExpandChanged_collapsing_logNoteTaskClosed() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = false) createNoteTaskController() - .showNoteTask( - isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE, + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = false, + key = Bubble.KEY_APP_BUBBLE, ) - val intentCaptor = argumentCaptor<Intent>() - verify(context).startActivity(capture(intentCaptor)) - intentCaptor.value.let { intent -> - assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE) - assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) - assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) - assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() - } - verifyZeroInteractions(bubbles) - verify(uiEventLogger) - .log( - ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE, - NOTES_UID, - NOTES_PACKAGE_NAME + verify(eventLogger).logNoteTaskClosed(expectedInfo) + verifyZeroInteractions(context, bubbles, keyguardManager, userManager) + } + + @Test + fun onBubbleExpandChanged_expandingAndKeyguardLocked_doNothing() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true, isInMultiWindowMode = false) + + createNoteTaskController() + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = true, + key = Bubble.KEY_APP_BUBBLE, ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) } @Test - fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesAndLogUiEvent() { - whenever(keyguardManager.isKeyguardLocked).thenReturn(false) + fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_doNothing() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true, isInMultiWindowMode = false) createNoteTaskController() - .showNoteTask( + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = false, + key = Bubble.KEY_APP_BUBBLE, + ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) + } + + @Test + fun onBubbleExpandChanged_expandingAndInMultiWindowMode_doNothing() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = true) + + createNoteTaskController() + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = true, + key = Bubble.KEY_APP_BUBBLE, + ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager) + } + + @Test + fun onBubbleExpandChanged_notExpandingAndInMultiWindowMode_doNothing() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = true) + + createNoteTaskController() + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = false, + key = Bubble.KEY_APP_BUBBLE, + ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) + } + + @Test + fun onBubbleExpandChanged_notKeyAppBubble_shouldDoNothing() { + createNoteTaskController() + .onBubbleExpandChanged( + isExpanding = true, + key = "any other key", + ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) + } + + @Test + fun onBubbleExpandChanged_flagDisabled_shouldDoNothing() { + createNoteTaskController(isEnabled = false) + .onBubbleExpandChanged( + isExpanding = true, + key = Bubble.KEY_APP_BUBBLE, + ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) + } + // endregion + + // region showNoteTask + @Test + fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() { + val expectedInfo = + noteTaskInfo.copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, + isKeyguardLocked = true, + ) + whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) + whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) + + createNoteTaskController() + .showNoteTask( + entryPoint = expectedInfo.entryPoint!!, + isInMultiWindowMode = expectedInfo.isInMultiWindowMode, ) - verifyZeroInteractions(context) val intentCaptor = argumentCaptor<Intent>() - verify(bubbles).showOrHideAppBubble(capture(intentCaptor)) + verify(context).startActivity(capture(intentCaptor)) intentCaptor.value.let { intent -> assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE) assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() } - verify(uiEventLogger) - .log( - ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, - NOTES_UID, - NOTES_PACKAGE_NAME - ) + verify(eventLogger).logNoteTaskOpened(expectedInfo) + verifyZeroInteractions(bubbles) } @Test - fun showNoteTask_keyguardIsUnlocked_uiEventIsNull_shouldStartBubblesWithoutLoggingUiEvent() { - whenever(keyguardManager.isKeyguardLocked).thenReturn(false) + fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesWithoutLoggingUiEvent() { + val expectedInfo = + noteTaskInfo.copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isInMultiWindowMode = false, + isKeyguardLocked = false, + ) + whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) + whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) - createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null) + createNoteTaskController() + .showNoteTask( + entryPoint = expectedInfo.entryPoint!!, + isInMultiWindowMode = expectedInfo.isInMultiWindowMode, + ) verifyZeroInteractions(context) val intentCaptor = argumentCaptor<Intent>() @@ -159,17 +249,24 @@ internal class NoteTaskControllerTest : SysuiTestCase() { assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() } - verifyZeroInteractions(uiEventLogger) + verifyZeroInteractions(eventLogger) } @Test fun showNoteTask_isInMultiWindowMode_shouldStartActivityAndLogUiEvent() { - whenever(keyguardManager.isKeyguardLocked).thenReturn(false) + val expectedInfo = + noteTaskInfo.copy( + entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT, + isInMultiWindowMode = true, + isKeyguardLocked = false, + ) + whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) + whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) createNoteTaskController() .showNoteTask( - isInMultiWindowMode = true, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, + entryPoint = expectedInfo.entryPoint!!, + isInMultiWindowMode = expectedInfo.isInMultiWindowMode, ) val intentCaptor = argumentCaptor<Intent>() @@ -180,69 +277,65 @@ internal class NoteTaskControllerTest : SysuiTestCase() { assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() } + verify(eventLogger).logNoteTaskOpened(expectedInfo) verifyZeroInteractions(bubbles) - verify(uiEventLogger) - .log(ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, NOTES_UID, NOTES_PACKAGE_NAME) } @Test fun showNoteTask_bubblesIsNull_shouldDoNothing() { - whenever(optionalBubbles.orElse(null)).thenReturn(null) - - createNoteTaskController() + createNoteTaskController(bubbles = null) .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } @Test fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() { - whenever(optionalKeyguardManager.orElse(null)).thenReturn(null) - - createNoteTaskController() + createNoteTaskController(keyguardManager = null) .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } @Test fun showNoteTask_userManagerIsNull_shouldDoNothing() { - whenever(optionalUserManager.orElse(null)).thenReturn(null) - - createNoteTaskController() + createNoteTaskController(userManager = null) .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } @Test fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() { - whenever(resolver.resolveInfo()).thenReturn(null) + whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(null) createNoteTaskController() .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } @Test fun showNoteTask_flagDisabled_shouldDoNothing() { createNoteTaskController(isEnabled = false) - .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON) + .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isInMultiWindowMode = false, + ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } @Test @@ -251,11 +344,11 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController() .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } // endregion diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt new file mode 100644 index 000000000000..a4df346776a0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt @@ -0,0 +1,160 @@ +/* + * 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 android.test.suitebuilder.annotation.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.internal.logging.UiEventLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED +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 org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +/** atest SystemUITests:MonitoringNoteTaskEventListenerTest */ +@SmallTest +@RunWith(AndroidJUnit4::class) +internal class NoteTaskEventLoggerTest : SysuiTestCase() { + + @Mock lateinit var uiEventLogger: UiEventLogger + + private fun createNoteTaskEventLogger(): NoteTaskEventLogger = + NoteTaskEventLogger(uiEventLogger) + + private fun createNoteTaskInfo(): NoteTaskInfo = + NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + // region logNoteTaskOpened + @Test + fun logNoteTaskOpened_entryPointWidgetPickerShortcut_noteOpenedViaShortcut() { + val info = createNoteTaskInfo().copy(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + val expected = NOTE_OPENED_VIA_SHORTCUT + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun onNoteTaskBubbleExpanded_entryPointQuickAffordance_noteOpenedViaQuickAffordance() { + val info = createNoteTaskInfo().copy(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + val expected = NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun onNoteTaskBubbleExpanded_entryPointTailButtonAndIsKeyguardUnlocked_noteOpenedViaTailButtonUnlocked() { // ktlint-disable max-line-length + val info = + createNoteTaskInfo() + .copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isKeyguardLocked = false, + ) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + val expected = NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun onNoteTaskBubbleExpanded_entryPointTailButtonAndIsKeyguardLocked_noteOpenedViaTailButtonLocked() { // ktlint-disable max-line-length + val info = + createNoteTaskInfo() + .copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isKeyguardLocked = true, + ) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + val expected = NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun onNoteTaskBubbleExpanded_noEntryPoint_noLog() { + val info = createNoteTaskInfo().copy(entryPoint = null) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + verifyNoMoreInteractions(uiEventLogger) + } + // endregion + + // region logNoteTaskClosed + @Test + fun logNoteTaskClosed_entryPointTailButton_noteClosedViaTailButtonUnlocked() { + val info = + createNoteTaskInfo() + .copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isKeyguardLocked = false, + ) + + createNoteTaskEventLogger().logNoteTaskClosed(info) + + val expected = NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun logNoteTaskClosed_entryPointTailButtonAndKeyguardLocked_noteClosedViaTailButtonLocked() { + val info = + createNoteTaskInfo() + .copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isKeyguardLocked = true, + ) + + createNoteTaskEventLogger().logNoteTaskClosed(info) + + val expected = NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun logNoteTaskClosed_noEntryPoint_noLog() { + val info = createNoteTaskInfo().copy(entryPoint = null) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + verifyNoMoreInteractions(uiEventLogger) + } + // endregion + + private companion object { + const val NOTES_PACKAGE_NAME = "com.android.note.app" + const val NOTES_UID = 123456 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt new file mode 100644 index 000000000000..7e975b6732a5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt @@ -0,0 +1,73 @@ +/* + * 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 android.test.suitebuilder.annotation.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +/** atest SystemUITests:NoteTaskInfoTest */ +@SmallTest +@RunWith(AndroidJUnit4::class) +internal class NoteTaskInfoTest : SysuiTestCase() { + + private fun createNoteTaskInfo(): NoteTaskInfo = + NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID) + + @Test + fun launchMode_notInMultiWindowModeAndKeyguardUnlocked_launchModeAppBubble() { + val underTest = + createNoteTaskInfo() + .copy( + isKeyguardLocked = false, + isInMultiWindowMode = false, + ) + + assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble) + } + + @Test + fun launchMode_inMultiWindowMode_launchModeActivity() { + val underTest = + createNoteTaskInfo() + .copy( + isKeyguardLocked = false, + isInMultiWindowMode = true, + ) + + assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity) + } + + @Test + fun launchMode_keyguardLocked_launchModeActivity() { + val underTest = + createNoteTaskInfo() + .copy( + isKeyguardLocked = true, + isInMultiWindowMode = false, + ) + + assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity) + } + + private companion object { + const val NOTES_PACKAGE_NAME = "com.android.note.app" + const val NOTES_UID = 123456 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index 53720ffdff94..a8eab50e0798 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -15,15 +15,11 @@ */ package com.android.systemui.notetask -import android.app.KeyguardManager 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.NoteTaskController.ShowNoteTaskUiEvent 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.bubbles.Bubbles import java.util.Optional import org.junit.Before @@ -48,27 +44,22 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { @Mock lateinit var commandQueue: CommandQueue @Mock lateinit var bubbles: Bubbles - @Mock lateinit var optionalBubbles: Optional<Bubbles> @Mock lateinit var noteTaskController: NoteTaskController @Before fun setUp() { MockitoAnnotations.initMocks(this) - - whenever(optionalBubbles.isPresent).thenReturn(true) - whenever(optionalBubbles.orElse(null)).thenReturn(bubbles) } private fun createNoteTaskInitializer( isEnabled: Boolean = true, - optionalKeyguardManager: Optional<KeyguardManager> = Optional.empty(), + bubbles: Bubbles? = this.bubbles, ): NoteTaskInitializer { return NoteTaskInitializer( - optionalBubbles = optionalBubbles, - noteTaskController = noteTaskController, + controller = noteTaskController, commandQueue = commandQueue, + optionalBubbles = Optional.ofNullable(bubbles), isEnabled = isEnabled, - optionalKeyguardManager = optionalKeyguardManager, ) } @@ -89,9 +80,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { @Test fun initialize_bubblesNotPresent_shouldDoNothing() { - whenever(optionalBubbles.isPresent).thenReturn(false) - - createNoteTaskInitializer().initialize() + createNoteTaskInitializer(bubbles = null).initialize() verify(commandQueue, never()).addCallback(any()) } @@ -113,37 +102,12 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { // region handleSystemKey @Test - fun handleSystemKey_receiveValidSystemKey_keyguardNotLocked_shouldShowNoteTaskWithUnlocked() { - val keyguardManager = - mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(false) } - createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager)) - .callbacks - .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT) - - verify(noteTaskController) - .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON) - } - - @Test - fun handleSystemKey_receiveValidSystemKey_keyguardLocked_shouldShowNoteTaskWithLocked() { - val keyguardManager = - mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(true) } - createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager)) - .callbacks - .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT) - - verify(noteTaskController) - .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED) - } - - @Test - fun handleSystemKey_receiveValidSystemKey_nullKeyguardManager_shouldShowNoteTaskWithUnlocked() { - createNoteTaskInitializer(optionalKeyguardManager = Optional.empty()) + fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() { + createNoteTaskInitializer() .callbacks .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT) - verify(noteTaskController) - .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON) + verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt index cdc683f8f8f8..e57d0d943aab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt @@ -27,7 +27,7 @@ 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.notetask.NoteTaskController.ShowNoteTaskUiEvent +import com.android.systemui.notetask.NoteTaskEntryPoint import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -95,7 +95,6 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { underTest.onTriggered(expandable = null) - verify(noteTaskController) - .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE) + verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 38e82281856f..db4bb45f75e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -114,6 +114,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; +import com.android.systemui.notetask.NoteTaskController; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; @@ -259,6 +260,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private StatusBarWindowStateController mStatusBarWindowStateController; @Mock private UserSwitcherController mUserSwitcherController; @Mock private Bubbles mBubbles; + @Mock private NoteTaskController mNoteTaskController; @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Mock private NotificationIconAreaController mNotificationIconAreaController; @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController; @@ -472,6 +474,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mWakefulnessLifecycle, mStatusBarStateController, Optional.of(mBubbles), + () -> mNoteTaskController, mDeviceProvisionedController, mNavigationBarController, mAccessibilityFloatingMenuController, |