1 /*
<lambda>null2 * Copyright (C) 2025 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.notification.promoted
18
19 import android.app.Flags.notificationsRedesignTemplates
20 import android.app.Notification
21 import android.content.Context
22 import android.graphics.PorterDuff
23 import android.util.Log
24 import android.view.LayoutInflater
25 import android.view.NotificationTopLineView
26 import android.view.View
27 import android.view.View.GONE
28 import android.view.View.MeasureSpec.AT_MOST
29 import android.view.View.MeasureSpec.EXACTLY
30 import android.view.View.MeasureSpec.UNSPECIFIED
31 import android.view.View.MeasureSpec.makeMeasureSpec
32 import android.view.View.VISIBLE
33 import android.view.ViewGroup.MarginLayoutParams
34 import android.view.ViewStub
35 import android.widget.Chronometer
36 import android.widget.DateTimeView
37 import android.widget.FrameLayout
38 import android.widget.ImageView
39 import android.widget.ProgressBar
40 import android.widget.TextView
41 import androidx.annotation.DimenRes
42 import androidx.compose.foundation.BorderStroke
43 import androidx.compose.foundation.border
44 import androidx.compose.foundation.layout.Box
45 import androidx.compose.foundation.layout.PaddingValues
46 import androidx.compose.foundation.layout.padding
47 import androidx.compose.foundation.shape.RoundedCornerShape
48 import androidx.compose.runtime.Composable
49 import androidx.compose.runtime.key
50 import androidx.compose.ui.Modifier
51 import androidx.compose.ui.graphics.SolidColor
52 import androidx.compose.ui.platform.LocalDensity
53 import androidx.compose.ui.res.dimensionResource
54 import androidx.compose.ui.unit.Dp
55 import androidx.compose.ui.unit.dp
56 import androidx.compose.ui.viewinterop.AndroidView
57 import androidx.core.view.isVisible
58 import com.android.app.tracing.traceSection
59 import com.android.internal.R
60 import com.android.internal.widget.BigPictureNotificationImageView
61 import com.android.internal.widget.CachingIconView
62 import com.android.internal.widget.ImageFloatingTextView
63 import com.android.internal.widget.NotificationExpandButton
64 import com.android.internal.widget.NotificationProgressBar
65 import com.android.internal.widget.NotificationProgressModel
66 import com.android.internal.widget.NotificationRowIconView
67 import com.android.systemui.lifecycle.rememberViewModel
68 import com.android.systemui.res.R as systemuiR
69 import com.android.systemui.statusbar.notification.promoted.AodPromotedNotificationColor.Background
70 import com.android.systemui.statusbar.notification.promoted.AodPromotedNotificationColor.PrimaryText
71 import com.android.systemui.statusbar.notification.promoted.AodPromotedNotificationColor.SecondaryText
72 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
73 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
74 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
75 import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel
76 import com.android.systemui.statusbar.notification.row.shared.ImageModel
77 import com.android.systemui.statusbar.notification.row.shared.isNullOrEmpty
78
79 @Composable
80 fun AODPromotedNotification(
81 viewModelFactory: AODPromotedNotificationViewModel.Factory,
82 modifier: Modifier = Modifier,
83 ) {
84 if (!PromotedNotificationUi.isEnabled) {
85 return
86 }
87
88 val viewModel = rememberViewModel(traceName = "$TAG.viewModel") { viewModelFactory.create() }
89
90 val content = viewModel.content ?: return
91 val audiblyAlertedIconVisible = viewModel.audiblyAlertedIconVisible
92
93 val layoutResource = content.layoutResource
94 if (layoutResource == null) {
95 Log.w(TAG, "not displaying promoted notif with ineligible style on AOD")
96 return
97 }
98
99 key(content.identity) {
100 AODPromotedNotificationView(
101 layoutResource = layoutResource,
102 content = content,
103 audiblyAlertedIconVisible = audiblyAlertedIconVisible,
104 modifier = modifier,
105 )
106 }
107 }
108
109 @Composable
AODPromotedNotificationViewnull110 fun AODPromotedNotificationView(
111 layoutResource: Int,
112 content: PromotedNotificationContentModel,
113 audiblyAlertedIconVisible: Boolean,
114 modifier: Modifier = Modifier,
115 ) {
116 val sidePaddings = dimensionResource(systemuiR.dimen.notification_side_paddings)
117 val sidePaddingValues = PaddingValues(horizontal = sidePaddings, vertical = 0.dp)
118
119 val boxModifier = modifier.padding(sidePaddingValues)
120
121 val borderStroke = BorderStroke(1.dp, SecondaryText.brush)
122
123 val borderRadius = dimensionResource(systemuiR.dimen.notification_corner_radius)
124 val borderShape = RoundedCornerShape(borderRadius)
125
126 val maxHeight =
127 with(LocalDensity.current) {
128 scaledFontHeight(systemuiR.dimen.notification_max_height_for_promoted_ongoing)
129 .toPx()
130 }
131 .toInt()
132
133 val viewModifier = Modifier.border(borderStroke, borderShape)
134
135 Box(modifier = boxModifier) {
136 AndroidView(
137 factory = { context ->
138 val notif =
139 traceSection("$TAG.inflate") {
140 LayoutInflater.from(context).inflate(layoutResource, /* root= */ null)
141 }
142 val updater =
143 traceSection("$TAG.findViews") { AODPromotedNotificationViewUpdater(notif) }
144
145 val frame = FrameLayoutWithMaxHeight(maxHeight, context)
146 frame.addView(notif)
147 frame.setTag(viewUpdaterTagId, updater)
148
149 frame
150 },
151 update = { frame ->
152 val updater = frame.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater
153
154 traceSection("$TAG.update") { updater.update(content, audiblyAlertedIconVisible) }
155 frame.maxHeight = maxHeight
156 },
157 modifier = viewModifier,
158 )
159 }
160 }
161
162 private class FrameLayoutWithMaxHeight(maxHeight: Int, context: Context) : FrameLayout(context) {
163 var maxHeight = maxHeight
164 set(value) {
165 if (field != value) {
166 field = value
167 requestLayout()
168 }
169 }
170
171 // This mirrors the logic in NotificationContentView.onMeasure.
onMeasurenull172 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
173 if (childCount != 1) {
174 Log.wtf(TAG, "Should contain exactly one child.")
175 return super.onMeasure(widthMeasureSpec, heightMeasureSpec)
176 }
177
178 val horizPadding = paddingStart + paddingEnd
179 val vertPadding = paddingTop + paddingBottom
180
181 val ownWidthSize = MeasureSpec.getSize(widthMeasureSpec)
182 val ownHeightMode = MeasureSpec.getMode(heightMeasureSpec)
183 val ownHeightSize = MeasureSpec.getSize(heightMeasureSpec)
184
185 val availableHeight =
186 if (ownHeightMode != UNSPECIFIED) {
187 maxHeight.coerceAtMost(ownHeightSize)
188 } else {
189 maxHeight
190 }
191
192 val child = getChildAt(0)
193 val childWidthSpec = makeMeasureSpec(ownWidthSize, EXACTLY)
194 val childHeightSpec =
195 child.layoutParams.height
196 .takeIf { it >= 0 }
197 ?.let { makeMeasureSpec(availableHeight.coerceAtMost(it), EXACTLY) }
198 ?: run { makeMeasureSpec(availableHeight, AT_MOST) }
199 measureChildWithMargins(child, childWidthSpec, horizPadding, childHeightSpec, vertPadding)
200 val childMeasuredHeight = child.measuredHeight
201
202 val ownMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec)
203 val ownMeasuredHeight =
204 if (ownHeightMode != UNSPECIFIED) {
205 childMeasuredHeight.coerceAtMost(ownHeightSize)
206 } else {
207 childMeasuredHeight
208 }
209 setMeasuredDimension(ownMeasuredWidth, ownMeasuredHeight)
210 }
211 }
212
213 private val PromotedNotificationContentModel.layoutResource: Int?
214 get() {
215 return if (notificationsRedesignTemplates()) {
216 when (style) {
217 Style.Base -> R.layout.notification_2025_template_expanded_base
218 Style.CollapsedBase -> R.layout.notification_2025_template_collapsed_base
219 Style.BigPicture -> R.layout.notification_2025_template_expanded_big_picture
220 Style.BigText -> R.layout.notification_2025_template_expanded_big_text
221 Style.Call -> R.layout.notification_2025_template_expanded_call
222 Style.CollapsedCall -> R.layout.notification_2025_template_collapsed_call
223 Style.Progress -> R.layout.notification_2025_template_expanded_progress
224 Style.Ineligible -> null
225 }
226 } else {
227 when (style) {
228 Style.Base -> R.layout.notification_template_material_big_base
229 Style.CollapsedBase -> R.layout.notification_template_material_base
230 Style.BigPicture -> R.layout.notification_template_material_big_picture
231 Style.BigText -> R.layout.notification_template_material_big_text
232 Style.Call -> R.layout.notification_template_material_big_call
233 Style.CollapsedCall -> R.layout.notification_template_material_call
234 Style.Progress -> R.layout.notification_template_material_progress
235 Style.Ineligible -> null
236 }
237 }
238 }
239
240 private class AODPromotedNotificationViewUpdater(root: View) {
241 private val alertedIcon: ImageView? = root.findViewById(R.id.alerted_icon)
242 private val alternateExpandTarget: View? = root.findViewById(R.id.alternate_expand_target)
243 private val appNameDivider: TextView? = root.findViewById(R.id.app_name_divider)
244 private val appNameText: TextView? = root.findViewById(R.id.app_name_text)
245 private val bigPicture: BigPictureNotificationImageView? = root.findViewById(R.id.big_picture)
246 private val bigText: ImageFloatingTextView? = root.findViewById(R.id.big_text)
247 private var chronometerStub: ViewStub? = root.findViewById(R.id.chronometer)
248 private var chronometer: Chronometer? = null
249 private val closeButton: View? = root.findViewById(R.id.close_button)
250 private val conversationIconBadge: View? = root.findViewById(R.id.conversation_icon_badge)
251 private val conversationIcon: CachingIconView? = root.findViewById(R.id.conversation_icon)
252 private val conversationText: TextView? =
253 root.findViewById(
254 if (notificationsRedesignTemplates()) R.id.title else R.id.conversation_text
255 )
256 private val expandButton: NotificationExpandButton? = root.findViewById(R.id.expand_button)
257 private val headerText: TextView? = root.findViewById(R.id.header_text)
258 private val headerTextDivider: TextView? = root.findViewById(R.id.header_text_divider)
259 private val headerTextSecondary: TextView? = root.findViewById(R.id.header_text_secondary)
260 private val headerTextSecondaryDivider: TextView? =
261 root.findViewById(R.id.header_text_secondary_divider)
262 private val icon: NotificationRowIconView? = root.findViewById(R.id.icon)
263 private val leftIcon: ImageView? = root.findViewById(R.id.left_icon)
264 private val mainColumn: View? = root.findViewById(R.id.notification_main_column)
265 private val notificationProgressEndIcon: CachingIconView? =
266 root.findViewById(R.id.notification_progress_end_icon)
267 private val notificationProgressStartIcon: CachingIconView? =
268 root.findViewById(R.id.notification_progress_start_icon)
269 private val profileBadge: ImageView? = root.findViewById(R.id.profile_badge)
270 private val rightIcon: ImageView? = root.findViewById(R.id.right_icon)
271 private val text: ImageFloatingTextView? = root.findViewById(R.id.text)
272 private val time: DateTimeView? = root.findViewById(R.id.time)
273 private val timeDivider: TextView? = root.findViewById(R.id.time_divider)
274 private val title: TextView? = root.findViewById(R.id.title)
275 private val topLine: NotificationTopLineView? = root.findViewById(R.id.notification_top_line)
276 private val verificationDivider: TextView? = root.findViewById(R.id.verification_divider)
277 private val verificationIcon: ImageView? = root.findViewById(R.id.verification_icon)
278 private val verificationText: TextView? = root.findViewById(R.id.verification_text)
279
280 private var oldProgressBarStub = root.findViewById<View>(R.id.progress) as? ViewStub
281 private var oldProgressBar: ProgressBar? = null
282 private val newProgressBar = root.findViewById<View>(R.id.progress) as? NotificationProgressBar
283
284 private val largeIconSizePx: Int =
285 root.context.resources.getDimensionPixelSize(R.dimen.notification_right_icon_size)
286
287 private val endMarginPx: Int =
288 if (notificationsRedesignTemplates()) {
289 root.context.resources.getDimensionPixelSize(R.dimen.notification_2025_margin)
290 } else {
291 root.context.resources.getDimensionPixelSize(
292 systemuiR.dimen.notification_shade_content_margin_horizontal
293 )
294 }
295
296 private val imageEndMarginPx: Int
297 get() = largeIconSizePx + 2 * endMarginPx
298
299 private val PromotedNotificationContentModel.imageEndMarginPxIfHasLargeIcon: Int
300 get() =
301 if (!skeletonLargeIcon.isNullOrEmpty()) {
302 imageEndMarginPx
303 } else {
304 0
305 }
306
307 init {
308 // Hide views that are never visible in the skeleton promoted notification.
309 alternateExpandTarget?.visibility = GONE
310 bigPicture?.visibility = GONE
311 closeButton?.visibility = GONE
312 conversationIconBadge?.visibility = GONE
313 expandButton?.visibility = GONE
314 leftIcon?.visibility = GONE
315 notificationProgressEndIcon?.visibility = GONE
316 notificationProgressStartIcon?.visibility = GONE
317
318 // Make one-time changes needed for the skeleton promoted notification.
319 alertedIcon
320 ?.drawable
321 ?.mutate()
322 ?.setColorFilter(SecondaryText.colorInt, PorterDuff.Mode.SRC_IN)
323
<lambda>null324 (rightIcon?.layoutParams as? MarginLayoutParams)?.let {
325 it.marginEnd = endMarginPx
326 rightIcon.layoutParams = it
327 }
328 bigText?.setImageEndMargin(largeIconSizePx + endMarginPx)
329 text?.setImageEndMargin(largeIconSizePx + endMarginPx)
330
331 setTextViewColor(appNameDivider, SecondaryText)
332 setTextViewColor(headerTextDivider, SecondaryText)
333 setTextViewColor(headerTextSecondaryDivider, SecondaryText)
334 setTextViewColor(timeDivider, SecondaryText)
335 setTextViewColor(verificationDivider, SecondaryText)
336
337 if (notificationsRedesignTemplates()) {
mainColumnMarginsnull338 (mainColumn?.layoutParams as? MarginLayoutParams)?.let { mainColumnMargins ->
339 mainColumnMargins.topMargin =
340 Notification.Builder.getContentMarginTop(
341 root.context,
342 R.dimen.notification_2025_content_margin_top,
343 )
344 }
345 }
346 }
347
updatenull348 fun update(content: PromotedNotificationContentModel, audiblyAlertedIconVisible: Boolean) {
349 when (content.style) {
350 Style.Base -> updateBase(content, collapsed = false)
351 Style.CollapsedBase -> updateBase(content, collapsed = true)
352 Style.BigPicture -> updateBigPictureStyle(content)
353 Style.BigText -> updateBigTextStyle(content)
354 Style.Call -> updateCallStyle(content, collapsed = false)
355 Style.CollapsedCall -> updateCallStyle(content, collapsed = true)
356 Style.Progress -> updateProgressStyle(content)
357 Style.Ineligible -> {}
358 }
359
360 alertedIcon?.isVisible = audiblyAlertedIconVisible
361 }
362
updateBasenull363 private fun updateBase(
364 content: PromotedNotificationContentModel,
365 collapsed: Boolean,
366 textView: ImageFloatingTextView? = text,
367 ) {
368 val headerTitleView = if (collapsed) title else null
369 updateHeader(content, titleView = headerTitleView, collapsed = collapsed)
370
371 if (headerTitleView == null) {
372 updateTitle(title, content)
373 }
374 updateText(textView, content)
375 updateSmallIcon(icon, content)
376 updateImageView(rightIcon, content.skeletonLargeIcon)
377 updateOldProgressBar(content)
378 }
379
updateBigPictureStylenull380 private fun updateBigPictureStyle(content: PromotedNotificationContentModel) {
381 updateBase(content, collapsed = false)
382 }
383
updateBigTextStylenull384 private fun updateBigTextStyle(content: PromotedNotificationContentModel) {
385 updateBase(content, collapsed = false, textView = bigText)
386 }
387
updateCallStylenull388 private fun updateCallStyle(content: PromotedNotificationContentModel, collapsed: Boolean) {
389 updateConversationHeader(content, collapsed = collapsed)
390
391 updateText(text, content)
392 }
393
updateProgressStylenull394 private fun updateProgressStyle(content: PromotedNotificationContentModel) {
395 updateBase(content, collapsed = false)
396
397 updateNewProgressBar(content)
398 }
399
updateOldProgressBarnull400 private fun updateOldProgressBar(content: PromotedNotificationContentModel) {
401 if (
402 content.style == Style.Progress ||
403 content.oldProgress == null ||
404 content.oldProgress.max == 0 ||
405 content.oldProgress.isIndeterminate
406 ) {
407 oldProgressBar?.visibility = GONE
408 return
409 }
410
411 inflateOldProgressBar()
412
413 val oldProgressBar = oldProgressBar ?: return
414
415 oldProgressBar.progress = content.oldProgress.progress
416 oldProgressBar.max = content.oldProgress.max
417 oldProgressBar.isIndeterminate = content.oldProgress.isIndeterminate
418 oldProgressBar.visibility = VISIBLE
419 }
420
updateNewProgressBarnull421 private fun updateNewProgressBar(content: PromotedNotificationContentModel) {
422 val newProgressBar = newProgressBar ?: return
423
424 if (content.newProgress != null && !content.newProgress.isIndeterminate) {
425 newProgressBar.setProgressModel(content.newProgress.toSkeleton().toBundle())
426 newProgressBar.visibility = VISIBLE
427 } else {
428 newProgressBar.visibility = GONE
429 }
430 }
431
updateHeadernull432 private fun updateHeader(
433 content: PromotedNotificationContentModel,
434 collapsed: Boolean,
435 titleView: TextView?,
436 ) {
437 val hasTitle = titleView != null && content.title != null
438 val hasSubText = content.subText != null
439 // the collapsed form doesn't show the app name unless there is no other text in the header
440 val appNameRequired = !hasTitle && !hasSubText
441 val hideAppName = (!appNameRequired && collapsed)
442
443 updateAppName(content, forceHide = hideAppName)
444 updateTextView(headerTextSecondary, content.subText)
445 updateTitle(titleView, content)
446 updateTimeAndChronometer(content)
447
448 updateHeaderDividers(content, hideTitle = !hasTitle, hideAppName = hideAppName)
449
450 updateTopLine(content)
451 }
452
updateHeaderDividersnull453 private fun updateHeaderDividers(
454 content: PromotedNotificationContentModel,
455 hideAppName: Boolean,
456 hideTitle: Boolean,
457 ) {
458 val hasAppName = content.appName != null && !hideAppName
459 val hasSubText = content.subText != null
460 val hasHeader = content.title != null && !hideTitle
461 val hasTimeOrChronometer = content.time != null
462
463 val hasTextBeforeSubText = hasAppName
464 val hasTextBeforeHeader = hasAppName || hasSubText
465 val hasTextBeforeTime = hasAppName || hasSubText || hasHeader
466
467 val showDividerBeforeSubText = hasTextBeforeSubText && hasSubText
468 val showDividerBeforeHeader = hasTextBeforeHeader && hasHeader
469 val showDividerBeforeTime = hasTextBeforeTime && hasTimeOrChronometer
470
471 headerTextSecondaryDivider?.isVisible = showDividerBeforeSubText
472 headerTextDivider?.isVisible = showDividerBeforeHeader
473 timeDivider?.isVisible = showDividerBeforeTime
474 }
475
updateConversationHeadernull476 private fun updateConversationHeader(
477 content: PromotedNotificationContentModel,
478 collapsed: Boolean,
479 ) {
480 updateAppName(content, forceHide = collapsed)
481 updateTimeAndChronometer(content)
482
483 updateImageView(verificationIcon, content.verificationIcon)
484 updateTextView(verificationText, content.verificationText)
485
486 updateConversationHeaderDividers(content, hideTitle = true, hideAppName = collapsed)
487
488 updateTopLine(content)
489
490 updateSmallIcon(conversationIcon, content)
491 updateTitle(conversationText, content)
492 }
493
updateConversationHeaderDividersnull494 private fun updateConversationHeaderDividers(
495 content: PromotedNotificationContentModel,
496 hideTitle: Boolean,
497 hideAppName: Boolean,
498 ) {
499 val hasTitle = content.title != null && !hideTitle
500 val hasAppName = content.appName != null && !hideAppName
501 val hasTimeOrChronometer = content.time != null
502 val hasVerification =
503 !content.verificationIcon.isNullOrEmpty() || content.verificationText != null
504
505 val hasTextBeforeAppName = hasTitle
506 val hasTextBeforeTime = hasTitle || hasAppName
507 val hasTextBeforeVerification = hasTitle || hasAppName || hasTimeOrChronometer
508
509 val showDividerBeforeAppName = hasTextBeforeAppName && hasAppName
510 val showDividerBeforeTime = hasTextBeforeTime && hasTimeOrChronometer
511 val showDividerBeforeVerification = hasTextBeforeVerification && hasVerification
512
513 appNameDivider?.isVisible = showDividerBeforeAppName
514 timeDivider?.isVisible = showDividerBeforeTime
515 verificationDivider?.isVisible = showDividerBeforeVerification
516 }
517
updateAppNamenull518 private fun updateAppName(content: PromotedNotificationContentModel, forceHide: Boolean) {
519 updateTextView(appNameText, content.appName?.takeUnless { forceHide })
520 }
521
updateTitlenull522 private fun updateTitle(titleView: TextView?, content: PromotedNotificationContentModel) {
523 (titleView?.layoutParams as? MarginLayoutParams)?.let {
524 it.marginEnd = content.imageEndMarginPxIfHasLargeIcon
525 titleView.layoutParams = it
526 }
527 updateTextView(titleView, content.title, color = PrimaryText)
528 }
529
updateTimeAndChronometernull530 private fun updateTimeAndChronometer(content: PromotedNotificationContentModel) {
531 setTextViewColor(time, SecondaryText)
532 setTextViewColor(chronometer, SecondaryText)
533
534 if (content.time is When.Time) {
535 time?.setTime(content.time.currentTimeMillis)
536 }
537
538 if (content.time is When.Chronometer) {
539 inflateChronometer()
540 chronometer?.base = content.time.elapsedRealtimeMillis
541 chronometer?.isCountDown = content.time.isCountDown
542 chronometer?.setStarted(true)
543 } else {
544 chronometer?.stop()
545 }
546
547 time?.isVisible = (content.time is When.Time)
548 chronometer?.isVisible = (content.time is When.Chronometer)
549 }
550
updateSmallIconnull551 private fun updateSmallIcon(
552 smallIconView: CachingIconView?,
553 content: PromotedNotificationContentModel,
554 ) {
555 smallIconView ?: return
556
557 // Icon binding must be called in this order
558 updateImageView(smallIconView, content.smallIcon)
559 smallIconView.setImageLevel(content.iconLevel)
560 smallIconView.setBackgroundColor(Background.colorInt)
561 smallIconView.originalIconColor = PrimaryText.colorInt
562 }
563
inflateChronometernull564 private fun inflateChronometer() {
565 if (chronometer != null) {
566 return
567 }
568
569 chronometer = chronometerStub?.inflate() as Chronometer
570 chronometerStub = null
571
572 chronometer?.appendFontFeatureSetting("tnum")
573 }
574
updateTopLinenull575 private fun updateTopLine(content: PromotedNotificationContentModel) {
576 topLine?.headerTextMarginEnd = content.imageEndMarginPxIfHasLargeIcon
577 }
578
inflateOldProgressBarnull579 private fun inflateOldProgressBar() {
580 if (oldProgressBar != null) {
581 return
582 }
583
584 oldProgressBar = oldProgressBarStub?.inflate() as ProgressBar
585 oldProgressBarStub = null
586 }
587
updateTextnull588 private fun updateText(
589 view: ImageFloatingTextView?,
590 content: PromotedNotificationContentModel,
591 ) {
592 view?.setHasImage(!content.skeletonLargeIcon.isNullOrEmpty())
593 view?.setNumIndentLines(if (content.title != null) 0 else 1)
594 updateTextView(view, content.text)
595 }
596
updateTextViewnull597 private fun updateTextView(
598 view: TextView?,
599 text: CharSequence?,
600 color: AodPromotedNotificationColor = SecondaryText,
601 ) {
602 if (view == null) return
603 setTextViewColor(view, color)
604
605 view.text = text?.toSkeleton() ?: ""
606 view.isVisible = !text.isNullOrEmpty()
607 }
608
updateImageViewnull609 private fun updateImageView(view: ImageView?, model: ImageModel?) {
610 if (view == null) return
611 val drawable = model?.drawable
612 view.setImageDrawable(drawable)
613 view.isVisible = drawable != null
614 }
615
setTextViewColornull616 private fun setTextViewColor(view: TextView?, color: AodPromotedNotificationColor) {
617 view?.setTextColor(color.colorInt)
618 }
619 }
620
toSkeletonnull621 private fun CharSequence.toSkeleton(): CharSequence {
622 return this.toString()
623 }
624
toSkeletonnull625 private fun NotificationProgressModel.toSkeleton(): NotificationProgressModel {
626 if (isIndeterminate) {
627 return NotificationProgressModel(/* indeterminateColor= */ SecondaryText.colorInt)
628 }
629
630 return NotificationProgressModel(
631 listOf(Notification.ProgressStyle.Segment(progressMax).toSkeleton()),
632 points.map { it.toSkeleton() }.toList(),
633 progress,
634 /* isStyledByProgress = */ true,
635 /* segmentsFallbackColor = */ SecondaryText.colorInt,
636 )
637 }
638
toSkeletonnull639 private fun Notification.ProgressStyle.Segment.toSkeleton(): Notification.ProgressStyle.Segment {
640 return Notification.ProgressStyle.Segment(length).also {
641 it.id = id
642 it.color = SecondaryText.colorInt
643 }
644 }
645
toSkeletonnull646 private fun Notification.ProgressStyle.Point.toSkeleton(): Notification.ProgressStyle.Point {
647 return Notification.ProgressStyle.Point(position).also {
648 it.id = id
649 it.color = SecondaryText.colorInt
650 }
651 }
652
appendFontFeatureSettingnull653 private fun TextView.appendFontFeatureSetting(newSetting: String) {
654 fontFeatureSettings = (fontFeatureSettings?.let { "$it," } ?: "") + newSetting
655 }
656
657 private enum class AodPromotedNotificationColor(val colorInt: Int) {
658 Background(android.graphics.Color.BLACK),
659 PrimaryText(android.graphics.Color.WHITE),
660 SecondaryText(android.graphics.Color.WHITE);
661
662 val brush = SolidColor(androidx.compose.ui.graphics.Color(colorInt))
663 }
664
665 @Composable
scaledFontHeightnull666 private fun scaledFontHeight(@DimenRes dimenId: Int): Dp {
667 return dimensionResource(dimenId) * LocalDensity.current.fontScale.coerceAtLeast(1f)
668 }
669
670 private val viewUpdaterTagId = systemuiR.id.aod_promoted_notification_view_updater_tag
671
672 private const val TAG = "AODPromotedNotification"
673