• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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