1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.notetask 18 19 import android.app.KeyguardManager 20 import android.content.ActivityNotFoundException 21 import android.content.ComponentName 22 import android.content.Context 23 import android.content.Intent 24 import android.content.pm.PackageManager 25 import android.os.UserManager 26 import android.util.Log 27 import com.android.internal.logging.UiEvent 28 import com.android.internal.logging.UiEventLogger 29 import com.android.systemui.dagger.SysUISingleton 30 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity 31 import com.android.systemui.util.kotlin.getOrNull 32 import com.android.wm.shell.bubbles.Bubbles 33 import java.util.Optional 34 import javax.inject.Inject 35 36 /** 37 * Entry point for creating and managing note. 38 * 39 * The controller decides how a note is launched based in the device state: locked or unlocked. 40 * 41 * Currently, we only support a single task per time. 42 */ 43 @SysUISingleton 44 internal class NoteTaskController 45 @Inject 46 constructor( 47 private val context: Context, 48 private val resolver: NoteTaskInfoResolver, 49 private val optionalBubbles: Optional<Bubbles>, 50 private val optionalKeyguardManager: Optional<KeyguardManager>, 51 private val optionalUserManager: Optional<UserManager>, 52 @NoteTaskEnabledKey private val isEnabled: Boolean, 53 private val uiEventLogger: UiEventLogger, 54 ) { 55 56 /** 57 * Shows a note task. How the task is shown will depend on when the method is invoked. 58 * 59 * If in multi-window mode, notes will open as a full screen experience. That is particularly 60 * important for Large screen devices. These devices may support a taskbar that let users to 61 * drag and drop a shortcut into multi-window mode, and notes should comply with this behaviour. 62 * 63 * If the keyguard is locked, notes will open as a full screen experience. A locked device has 64 * no contextual information which let us use the whole screen space available. 65 * 66 * If not in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be 67 * collapsed if the notes bubble is already opened. 68 * 69 * That will let users open other apps in full screen, and take contextual notes. 70 */ 71 @JvmOverloads showNoteTasknull72 fun showNoteTask(isInMultiWindowMode: Boolean = false, uiEvent: ShowNoteTaskUiEvent? = null) { 73 74 if (!isEnabled) return 75 76 val bubbles = optionalBubbles.getOrNull() ?: return 77 val keyguardManager = optionalKeyguardManager.getOrNull() ?: return 78 val userManager = optionalUserManager.getOrNull() ?: return 79 80 // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing. 81 if (!userManager.isUserUnlocked) return 82 83 val noteTaskInfo = resolver.resolveInfo() ?: return 84 85 uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) } 86 87 // TODO(b/266686199): We should handle when app not available. For now, we log. 88 val intent = noteTaskInfo.toCreateNoteIntent() 89 try { 90 if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) { 91 context.startActivity(intent) 92 } else { 93 bubbles.showOrHideAppBubble(intent) 94 } 95 } catch (e: ActivityNotFoundException) { 96 Log.e(TAG, "Activity not found for action: $ACTION_CREATE_NOTE.", e) 97 } 98 } 99 100 /** 101 * Set `android:enabled` property in the `AndroidManifest` associated with the Shortcut 102 * component to [value]. 103 * 104 * If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the 105 * Widget Picker to all users. 106 */ setNoteTaskShortcutEnablednull107 fun setNoteTaskShortcutEnabled(value: Boolean) { 108 val componentName = ComponentName(context, CreateNoteTaskShortcutActivity::class.java) 109 110 val enabledState = 111 if (value) { 112 PackageManager.COMPONENT_ENABLED_STATE_ENABLED 113 } else { 114 PackageManager.COMPONENT_ENABLED_STATE_DISABLED 115 } 116 117 context.packageManager.setComponentEnabledSetting( 118 componentName, 119 enabledState, 120 PackageManager.DONT_KILL_APP, 121 ) 122 } 123 124 /** IDs of UI events accepted by [showNoteTask]. */ 125 enum class ShowNoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum { 126 @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.") 127 NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294), 128 129 /* ktlint-disable max-line-length */ 130 @UiEvent( 131 doc = 132 "User opened a note by pressing the stylus tail button while the screen was unlocked." 133 ) 134 NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295), 135 @UiEvent( 136 doc = 137 "User opened a note by pressing the stylus tail button while the screen was locked." 138 ) 139 NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296), 140 @UiEvent(doc = "User opened a note by tapping on an app shortcut.") 141 NOTE_OPENED_VIA_SHORTCUT(1297); 142 getIdnull143 override fun getId() = _id 144 } 145 146 companion object { 147 private val TAG = NoteTaskController::class.simpleName.orEmpty() 148 149 private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent { 150 return Intent(ACTION_CREATE_NOTE) 151 .setPackage(packageName) 152 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 153 // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint 154 // was used to start it. 155 .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true) 156 } 157 158 // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead. 159 const val NOTE_TASK_KEY_EVENT = 311 160 161 // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead. 162 const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE" 163 164 // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead. 165 const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE" 166 } 167 } 168