1 /* 2 * Copyright (C) 2024 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.statusbar.chips.ui.model 18 19 import android.annotation.CurrentTimeMillisLong 20 import android.annotation.ElapsedRealtimeLong 21 import android.os.SystemClock 22 import android.view.View 23 import com.android.internal.logging.InstanceId 24 import com.android.systemui.animation.ComposableControllerFactory 25 import com.android.systemui.animation.Expandable 26 import com.android.systemui.common.shared.model.ContentDescription 27 import com.android.systemui.common.shared.model.Icon 28 import com.android.systemui.statusbar.StatusBarIconView 29 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips 30 import com.android.systemui.statusbar.chips.ui.viewmodel.TimeSource 31 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays 32 33 /** Model representing the display of an ongoing activity as a chip in the status bar. */ 34 sealed class OngoingActivityChipModel { 35 /** Condensed name representing the model, used for logs. */ 36 abstract val logName: String 37 38 /** Object used to manage the behavior of this chip during activity launch and returns. */ 39 abstract val transitionManager: TransitionManager? 40 41 /** 42 * This chip shouldn't be shown. 43 * 44 * @property shouldAnimate true if the transition from [Active] to [Inactive] should be 45 * animated, and false if that transition should *not* be animated (i.e. the chip view should 46 * immediately disappear). 47 */ 48 data class Inactive( 49 val shouldAnimate: Boolean = true, 50 override val transitionManager: TransitionManager? = null, 51 ) : OngoingActivityChipModel() { 52 override val logName = "Inactive(anim=$shouldAnimate)" 53 } 54 55 /** This chip should be shown with the given information. */ 56 sealed class Active( 57 /** 58 * A key that uniquely identifies this chip. Used for better visual effects, like animation. 59 */ 60 open val key: String, 61 /** 62 * True if this chip is critical for privacy so we should keep it visible at all times, and 63 * false otherwise. 64 */ 65 open val isImportantForPrivacy: Boolean = false, 66 /** The icon to show on the chip. If null, no icon will be shown. */ 67 open val icon: ChipIcon?, 68 /** What colors to use for the chip. */ 69 open val colors: ColorsModel, 70 /** 71 * Listener method to invoke when this chip is clicked. If null, the chip won't be 72 * clickable. Will be deprecated after [StatusBarChipsModernization] is enabled. 73 */ 74 open val onClickListenerLegacy: View.OnClickListener?, 75 /** Data class that determines how clicks on the chip should be handled. */ 76 open val clickBehavior: ClickBehavior, 77 override val transitionManager: TransitionManager?, 78 /** 79 * Whether this chip should be hidden. This can be the case depending on system states (like 80 * which apps are in the foreground and whether there is an ongoing transition. 81 */ 82 open val isHidden: Boolean, 83 /** Whether the transition from hidden to shown should be animated. */ 84 open val shouldAnimate: Boolean, 85 /** 86 * An optional per-chip ID used for logging. Should stay the same throughout the lifetime of 87 * a single chip. 88 */ 89 open val instanceId: InstanceId? = null, 90 ) : OngoingActivityChipModel() { 91 92 /** This chip shows only an icon and nothing else. */ 93 data class IconOnly( 94 override val key: String, 95 override val isImportantForPrivacy: Boolean = false, 96 override val icon: ChipIcon, 97 override val colors: ColorsModel, 98 override val onClickListenerLegacy: View.OnClickListener?, 99 override val clickBehavior: ClickBehavior, 100 override val transitionManager: TransitionManager? = null, 101 override val isHidden: Boolean = false, 102 override val shouldAnimate: Boolean = true, 103 override val instanceId: InstanceId? = null, 104 ) : 105 Active( 106 key, 107 isImportantForPrivacy, 108 icon, 109 colors, 110 onClickListenerLegacy, 111 clickBehavior, 112 transitionManager, 113 isHidden, 114 shouldAnimate, 115 instanceId, 116 ) { 117 override val logName = "Active.Icon" 118 } 119 120 /** The chip shows a timer, counting up from [startTimeMs]. */ 121 data class Timer( 122 override val key: String, 123 override val isImportantForPrivacy: Boolean = false, 124 override val icon: ChipIcon, 125 override val colors: ColorsModel, 126 /** 127 * The time this event started, used to show the timer. 128 * 129 * This time should be relative to 130 * [com.android.systemui.util.time.SystemClock.elapsedRealtime], *not* 131 * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because the 132 * [ChipChronometer] is based off of elapsed realtime. See 133 * [android.widget.Chronometer.setBase]. 134 */ 135 @ElapsedRealtimeLong val startTimeMs: Long, 136 137 /** 138 * The [TimeSource] that should be used to track the current time for this timer. Should 139 * be compatible units with [startTimeMs]. Only used in the Compose version of the 140 * chips. 141 */ <lambda>null142 val timeSource: TimeSource = TimeSource { SystemClock.elapsedRealtime() }, 143 144 /** 145 * True if this chip represents an event starting in the future and false if this chip 146 * represents an event that has already started. If true, [startTimeMs] should be in the 147 * future. Otherwise, [startTimeMs] should be in the past. 148 */ 149 val isEventInFuture: Boolean = false, 150 override val onClickListenerLegacy: View.OnClickListener?, 151 override val clickBehavior: ClickBehavior, 152 override val transitionManager: TransitionManager? = null, 153 override val isHidden: Boolean = false, 154 override val shouldAnimate: Boolean = true, 155 override val instanceId: InstanceId? = null, 156 ) : 157 Active( 158 key, 159 isImportantForPrivacy, 160 icon, 161 colors, 162 onClickListenerLegacy, 163 clickBehavior, 164 transitionManager, 165 isHidden, 166 shouldAnimate, 167 instanceId, 168 ) { 169 override val logName = "Active.Timer" 170 } 171 172 /** 173 * The chip shows the time delta between now and [time] in a short format, e.g. "15min" or 174 * "1hr ago". 175 */ 176 data class ShortTimeDelta( 177 override val key: String, 178 override val isImportantForPrivacy: Boolean = false, 179 override val icon: ChipIcon, 180 override val colors: ColorsModel, 181 /** 182 * The time of the event that this chip represents. Relative to 183 * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because that's what's 184 * required by [android.widget.DateTimeView]. 185 * 186 * TODO(b/372657935): When the Compose chips are launched, we should convert this to be 187 * relative to [com.android.systemui.util.time.SystemClock.elapsedRealtime] so that 188 * this model and the [Timer] model use the same units. 189 */ 190 @CurrentTimeMillisLong val time: Long, 191 192 /** 193 * The [TimeSource] that should be used to track the current time for this timer. Should 194 * be compatible units with [time]. Only used in the Compose version of the chips. 195 */ <lambda>null196 val timeSource: TimeSource = TimeSource { System.currentTimeMillis() }, 197 override val onClickListenerLegacy: View.OnClickListener?, 198 override val clickBehavior: ClickBehavior, 199 override val transitionManager: TransitionManager? = null, 200 override val isHidden: Boolean = false, 201 override val shouldAnimate: Boolean = true, 202 override val instanceId: InstanceId? = null, 203 ) : 204 Active( 205 key, 206 isImportantForPrivacy, 207 icon, 208 colors, 209 onClickListenerLegacy, 210 clickBehavior, 211 transitionManager, 212 isHidden, 213 shouldAnimate, 214 instanceId, 215 ) { 216 init { 217 StatusBarNotifChips.unsafeAssertInNewMode() 218 } 219 220 override val logName = "Active.ShortTimeDelta" 221 } 222 223 /** 224 * This chip shows a countdown using [secondsUntilStarted]. Used to inform users that an 225 * event is about to start. Typically, a [Countdown] chip will turn into a [Timer] chip. 226 */ 227 data class Countdown( 228 override val key: String, 229 override val isImportantForPrivacy: Boolean = false, 230 override val colors: ColorsModel, 231 /** The number of seconds until an event is started. */ 232 val secondsUntilStarted: Long, 233 override val transitionManager: TransitionManager? = null, 234 override val isHidden: Boolean = false, 235 override val shouldAnimate: Boolean = true, 236 override val instanceId: InstanceId? = null, 237 ) : 238 Active( 239 key, 240 isImportantForPrivacy, 241 icon = null, 242 colors, 243 onClickListenerLegacy = null, 244 clickBehavior = ClickBehavior.None, 245 transitionManager, 246 isHidden, 247 shouldAnimate, 248 instanceId, 249 ) { 250 override val logName = "Active.Countdown" 251 } 252 253 /** This chip shows the specified [text] in the chip. */ 254 data class Text( 255 override val key: String, 256 override val isImportantForPrivacy: Boolean = false, 257 override val icon: ChipIcon, 258 override val colors: ColorsModel, 259 val text: String, 260 override val onClickListenerLegacy: View.OnClickListener? = null, 261 override val clickBehavior: ClickBehavior, 262 override val transitionManager: TransitionManager? = null, 263 override val isHidden: Boolean = false, 264 override val shouldAnimate: Boolean = true, 265 override val instanceId: InstanceId? = null, 266 ) : 267 Active( 268 key, 269 isImportantForPrivacy, 270 icon, 271 colors, 272 onClickListenerLegacy, 273 clickBehavior, 274 transitionManager, 275 isHidden, 276 shouldAnimate, 277 instanceId, 278 ) { 279 override val logName = "Active.Text" 280 } 281 } 282 283 /** Represents an icon to show on the chip. */ 284 sealed class ChipIcon( 285 /** True if this icon will have padding embedded within its view. */ 286 open val hasEmbeddedPadding: Boolean 287 ) { 288 /** 289 * The icon is a custom icon, which is set on [impl]. The icon was likely created by an 290 * external app. 291 */ 292 data class StatusBarView( 293 val impl: StatusBarIconView, 294 val contentDescription: ContentDescription, 295 ) : ChipIcon(hasEmbeddedPadding = true) { 296 init { 297 StatusBarConnectedDisplays.assertInLegacyMode() 298 } 299 } 300 301 /** 302 * The icon is a custom icon, which is set on a notification, and can be looked up using the 303 * provided [notificationKey]. The icon was likely created by an external app. 304 */ 305 data class StatusBarNotificationIcon( 306 val notificationKey: String, 307 val contentDescription: ContentDescription, 308 ) : ChipIcon(hasEmbeddedPadding = true) { 309 init { 310 StatusBarConnectedDisplays.unsafeAssertInNewMode() 311 } 312 } 313 314 /** 315 * This icon is a single color and it came from basic resource or drawable icon that System 316 * UI created internally. 317 */ 318 data class SingleColorIcon(val impl: Icon) : ChipIcon(hasEmbeddedPadding = false) 319 } 320 321 /** Defines the behavior of the chip when it is clicked. */ 322 sealed interface ClickBehavior { 323 /** No specific click behavior. */ 324 data object None : ClickBehavior 325 326 /** The chip expands into a dialog or activity on click. */ 327 data class ExpandAction(val onClick: (Expandable) -> Unit) : ClickBehavior 328 329 /** Clicking the chip will show the heads up notification associated with the chip. */ 330 data class ShowHeadsUpNotification(val onClick: () -> Unit) : ClickBehavior 331 } 332 333 /** Defines the behavior of the chip with respect to activity launch and return transitions. */ 334 data class TransitionManager( 335 /** The factory used to create the controllers that animate the chip. */ 336 val controllerFactory: ComposableControllerFactory? = null, 337 /** 338 * Used to create a registration for this chip using [controllerFactory]. Must be 339 * idempotent. 340 */ <lambda>null341 val registerTransition: () -> Unit = {}, 342 /** Used to remove the existing registration for this chip, if any. */ <lambda>null343 val unregisterTransition: () -> Unit = {}, 344 /** 345 * Whether the chip should be made invisible (0 opacity) while still being composed. This is 346 * necessary to avoid flickers at the beginning of return transitions, when the chip must 347 * not be visible but must be composed in order for the animation to start. 348 */ 349 val hideChipForTransition: Boolean = false, 350 ) 351 } 352