1 /* 2 * Copyright (C) 2014 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.phone; 18 19 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; 20 import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard; 21 22 import android.annotation.ColorInt; 23 import android.content.Context; 24 import android.content.res.Configuration; 25 import android.content.res.Resources; 26 import android.graphics.Color; 27 import android.graphics.Insets; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 import android.os.Trace; 31 import android.util.AttributeSet; 32 import android.util.TypedValue; 33 import android.view.DisplayCutout; 34 import android.view.Gravity; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.view.WindowInsets; 38 import android.widget.ImageView; 39 import android.widget.LinearLayout; 40 import android.widget.RelativeLayout; 41 import android.widget.TextView; 42 43 import androidx.annotation.Nullable; 44 import androidx.annotation.VisibleForTesting; 45 46 import com.android.settingslib.Utils; 47 import com.android.systemui.battery.BatteryMeterView; 48 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; 49 import com.android.systemui.res.R; 50 import com.android.systemui.statusbar.core.NewStatusBarIcons; 51 import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider; 52 import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange; 53 import com.android.systemui.statusbar.phone.ui.TintedIconManager; 54 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; 55 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; 56 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; 57 58 import kotlinx.coroutines.flow.FlowKt; 59 import kotlinx.coroutines.flow.MutableStateFlow; 60 import kotlinx.coroutines.flow.StateFlow; 61 import kotlinx.coroutines.flow.StateFlowKt; 62 63 import java.io.PrintWriter; 64 import java.util.ArrayList; 65 import java.util.Objects; 66 67 /** 68 * The header group on Keyguard. 69 */ 70 public class KeyguardStatusBarView extends RelativeLayout { 71 72 private static final int LAYOUT_NONE = 0; 73 private static final int LAYOUT_CUTOUT = 1; 74 private static final int LAYOUT_NO_CUTOUT = 2; 75 76 private final ArrayList<Rect> mEmptyTintRect = new ArrayList<>(); 77 78 private boolean mShowPercentAvailable; 79 private boolean mBatteryCharging; 80 81 private TextView mCarrierLabel; 82 private ImageView mMultiUserAvatar; 83 @Nullable private BatteryMeterView mBatteryView; 84 private StatusIconContainer mStatusIconContainer; 85 private StatusBarUserSwitcherContainer mUserSwitcherContainer; 86 87 private boolean mKeyguardUserSwitcherEnabled; 88 private boolean mKeyguardUserAvatarEnabled; 89 90 private boolean mIsPrivacyDotEnabled; 91 private int mSystemIconsSwitcherHiddenExpandedMargin; 92 private int mStatusBarPaddingEnd; 93 private int mMinDotWidth; 94 private View mSystemIconsContainer; 95 private View mSystemIcons; 96 private final MutableStateFlow<DarkChange> mDarkChange = StateFlowKt.MutableStateFlow( 97 DarkChange.EMPTY); 98 99 private View mCutoutSpace; 100 private ViewGroup mStatusIconArea; 101 private int mLayoutState = LAYOUT_NONE; 102 103 /** 104 * Draw this many pixels into the left/right side of the cutout to optimally use the space 105 */ 106 private int mCutoutSideNudge = 0; 107 108 @Nullable 109 private WindowInsets mPreviousInsets = null; 110 111 private DisplayCutout mDisplayCutout; 112 private int mRoundedCornerPadding = 0; 113 // right and left padding applied to this view to account for cutouts and rounded corners 114 private Insets mPadding = Insets.of(0, 0, 0, 0); 115 116 /** 117 * The clipping on the top 118 */ 119 private int mTopClipping; 120 private final Rect mClipRect = new Rect(0, 0, 0, 0); 121 private boolean mIsUserSwitcherEnabled; 122 KeyguardStatusBarView(Context context, AttributeSet attrs)123 public KeyguardStatusBarView(Context context, AttributeSet attrs) { 124 super(context, attrs); 125 } 126 127 @Override onFinishInflate()128 protected void onFinishInflate() { 129 super.onFinishInflate(); 130 mSystemIconsContainer = findViewById(R.id.system_icons_container); 131 mSystemIcons = findViewById(R.id.system_icons); 132 mMultiUserAvatar = findViewById(R.id.multi_user_avatar); 133 mCarrierLabel = findViewById(R.id.keyguard_carrier_text); 134 mBatteryView = mSystemIconsContainer.findViewById(R.id.battery); 135 if (NewStatusBarIcons.isEnabled()) { 136 // When this flag is rolled forward, this whole view can be removed 137 mBatteryView.setVisibility(View.GONE); 138 mBatteryView = null; 139 } 140 mCutoutSpace = findViewById(R.id.cutout_space_view); 141 mStatusIconArea = findViewById(R.id.status_icon_area); 142 mStatusIconContainer = findViewById(R.id.statusIcons); 143 mUserSwitcherContainer = findViewById(R.id.user_switcher_container); 144 mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot); 145 loadDimens(); 146 } 147 148 /** 149 * Should only be called from {@link KeyguardStatusBarViewController} 150 * @param viewModel view model for the status bar user chip 151 */ init(StatusBarUserChipViewModel viewModel)152 void init(StatusBarUserChipViewModel viewModel) { 153 StatusBarUserChipViewBinder.bind(mUserSwitcherContainer, viewModel); 154 } 155 156 @Override onConfigurationChanged(Configuration newConfig)157 protected void onConfigurationChanged(Configuration newConfig) { 158 super.onConfigurationChanged(newConfig); 159 loadDimens(); 160 161 MarginLayoutParams lp = (MarginLayoutParams) mMultiUserAvatar.getLayoutParams(); 162 lp.width = lp.height = getResources().getDimensionPixelSize( 163 R.dimen.multi_user_avatar_keyguard_size); 164 mMultiUserAvatar.setLayoutParams(lp); 165 166 // System icons 167 updateSystemIconsLayoutParams(); 168 169 // mStatusIconArea 170 mStatusIconArea.setPaddingRelative( 171 mStatusIconArea.getPaddingStart(), 172 getResources().getDimensionPixelSize(R.dimen.status_bar_padding_top), 173 mStatusIconArea.getPaddingEnd(), 174 mStatusIconArea.getPaddingBottom() 175 ); 176 177 // mStatusIconContainer 178 mStatusIconContainer.setPaddingRelative( 179 mStatusIconContainer.getPaddingStart(), 180 mStatusIconContainer.getPaddingTop(), 181 getResources().getDimensionPixelSize(R.dimen.signal_cluster_battery_padding), 182 mStatusIconContainer.getPaddingBottom() 183 ); 184 185 mSystemIcons.setPaddingRelative( 186 getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start), 187 getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top), 188 getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end), 189 getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom) 190 ); 191 192 // Respect font size setting. 193 mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 194 getResources().getDimensionPixelSize( 195 com.android.internal.R.dimen.text_size_small_material)); 196 updateCarrierLabelMargin(); 197 198 updateKeyguardStatusBarHeight(); 199 } 200 setUserSwitcherEnabled(boolean enabled)201 public void setUserSwitcherEnabled(boolean enabled) { 202 mIsUserSwitcherEnabled = enabled; 203 } 204 updateKeyguardStatusBarHeight()205 private void updateKeyguardStatusBarHeight() { 206 ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams(); 207 lp.height = getStatusBarHeaderHeightKeyguard(mContext); 208 setLayoutParams(lp); 209 } 210 updateCarrierLabelMargin()211 private void updateCarrierLabelMargin() { 212 MarginLayoutParams lp = (MarginLayoutParams) mCarrierLabel.getLayoutParams(); 213 int marginStart = calculateMargin( 214 getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin), 215 mPadding.left); 216 lp.setMarginStart(marginStart); 217 mCarrierLabel.setLayoutParams(lp); 218 } 219 loadDimens()220 void loadDimens() { 221 Resources res = getResources(); 222 mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize( 223 R.dimen.system_icons_switcher_hidden_expanded_margin); 224 mStatusBarPaddingEnd = res.getDimensionPixelSize( 225 R.dimen.status_bar_padding_end); 226 mMinDotWidth = res.getDimensionPixelSize( 227 R.dimen.ongoing_appops_dot_min_padding); 228 mCutoutSideNudge = getResources().getDimensionPixelSize( 229 R.dimen.display_cutout_margin_consumption); 230 mShowPercentAvailable = getContext().getResources().getBoolean( 231 com.android.internal.R.bool.config_battery_percentage_setting_available); 232 mRoundedCornerPadding = res.getDimensionPixelSize( 233 R.dimen.rounded_corner_content_padding); 234 } 235 updateVisibilities()236 private void updateVisibilities() { 237 // Multi user avatar is disabled in favor of the user switcher chip 238 if (!mKeyguardUserAvatarEnabled) { 239 if (mMultiUserAvatar.getParent() == mStatusIconArea) { 240 mStatusIconArea.removeView(mMultiUserAvatar); 241 } else if (mMultiUserAvatar.getParent() != null) { 242 getOverlay().remove(mMultiUserAvatar); 243 } 244 245 return; 246 } 247 248 if (mMultiUserAvatar.getParent() != mStatusIconArea 249 && !mKeyguardUserSwitcherEnabled) { 250 if (mMultiUserAvatar.getParent() != null) { 251 getOverlay().remove(mMultiUserAvatar); 252 } 253 mStatusIconArea.addView(mMultiUserAvatar, 0); 254 } else if (mMultiUserAvatar.getParent() == mStatusIconArea 255 && mKeyguardUserSwitcherEnabled) { 256 mStatusIconArea.removeView(mMultiUserAvatar); 257 } 258 if (!mKeyguardUserSwitcherEnabled) { 259 // If we have no keyguard switcher, the screen width is under 600dp. In this case, 260 // we only show the multi-user switch if it's enabled through UserManager as well as 261 // by the user. 262 if (mIsUserSwitcherEnabled) { 263 mMultiUserAvatar.setVisibility(View.VISIBLE); 264 } else { 265 mMultiUserAvatar.setVisibility(View.GONE); 266 } 267 } 268 269 if (mBatteryView != null) { 270 mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable); 271 } 272 } 273 updateSystemIconsLayoutParams()274 private void updateSystemIconsLayoutParams() { 275 LinearLayout.LayoutParams lp = 276 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams(); 277 278 // Use status_bar_padding_end to replace original 279 // system_icons_super_container_avatarless_margin_end to prevent different end alignment 280 // between PhoneStatusBarView and KeyguardStatusBarView 281 int baseMarginEnd = mStatusBarPaddingEnd; 282 int marginEnd = 283 mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin 284 : baseMarginEnd; 285 286 // Align PhoneStatusBar right margin/padding, only use 287 // 1. status bar layout: mPadding(consider round_corner + privacy dot) 288 // 2. icon container: R.dimen.status_bar_padding_end 289 290 if (marginEnd != lp.getMarginEnd()) { 291 lp.setMarginEnd(marginEnd); 292 mSystemIconsContainer.setLayoutParams(lp); 293 } 294 } 295 296 /** Should only be called from {@link KeyguardStatusBarViewController}. */ updateWindowInsets( WindowInsets insets, StatusBarContentInsetsProvider insetsProvider)297 WindowInsets updateWindowInsets( 298 WindowInsets insets, 299 StatusBarContentInsetsProvider insetsProvider) { 300 if (!Objects.equals(mPreviousInsets, insets)) { 301 mLayoutState = LAYOUT_NONE; 302 if (updateLayoutConsideringCutout(insetsProvider)) { 303 requestLayout(); 304 } 305 mPreviousInsets = new WindowInsets(insets); 306 } 307 return super.onApplyWindowInsets(insets); 308 } 309 updateLayoutConsideringCutout(StatusBarContentInsetsProvider insetsProvider)310 private boolean updateLayoutConsideringCutout(StatusBarContentInsetsProvider insetsProvider) { 311 return setDisplayCutout( 312 getRootWindowInsets().getDisplayCutout(), 313 insetsProvider); 314 } 315 316 /** Sets the {@link DisplayCutout}, updating the view to render around the cutout. */ setDisplayCutout( @ullable DisplayCutout displayCutout, StatusBarContentInsetsProvider insetsProvider)317 public boolean setDisplayCutout( 318 @Nullable DisplayCutout displayCutout, 319 StatusBarContentInsetsProvider insetsProvider) { 320 mDisplayCutout = displayCutout; 321 updateKeyguardStatusBarHeight(); 322 updatePadding(insetsProvider); 323 if (mDisplayCutout == null || insetsProvider.currentRotationHasCornerCutout()) { 324 return updateLayoutParamsNoCutout(); 325 } else { 326 return updateLayoutParamsForCutout(); 327 } 328 } 329 updatePadding(StatusBarContentInsetsProvider insetsProvider)330 private void updatePadding(StatusBarContentInsetsProvider insetsProvider) { 331 final int waterfallTop = 332 mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top; 333 mPadding = insetsProvider.getStatusBarContentInsetsForCurrentRotation(); 334 335 // consider privacy dot space 336 final int minLeft = (isLayoutRtl() && mIsPrivacyDotEnabled) 337 ? Math.max(mMinDotWidth, mPadding.left) : mPadding.left; 338 final int minRight = (!isLayoutRtl() && mIsPrivacyDotEnabled) 339 ? Math.max(mMinDotWidth, mPadding.right) : mPadding.right; 340 341 int top = waterfallTop + mPadding.top; 342 setPadding(minLeft, top, minRight, 0); 343 } 344 updateLayoutParamsNoCutout()345 private boolean updateLayoutParamsNoCutout() { 346 if (mLayoutState == LAYOUT_NO_CUTOUT) { 347 return false; 348 } 349 mLayoutState = LAYOUT_NO_CUTOUT; 350 351 if (mCutoutSpace != null) { 352 mCutoutSpace.setVisibility(View.GONE); 353 } 354 355 RelativeLayout.LayoutParams lp = (LayoutParams) mCarrierLabel.getLayoutParams(); 356 lp.addRule(RelativeLayout.START_OF, R.id.status_icon_area); 357 updateCarrierLabelMargin(); 358 359 lp = (LayoutParams) mStatusIconArea.getLayoutParams(); 360 lp.removeRule(RelativeLayout.RIGHT_OF); 361 lp.width = LayoutParams.WRAP_CONTENT; 362 lp.setMarginStart(getResources().getDimensionPixelSize( 363 R.dimen.system_icons_super_container_margin_start)); 364 return true; 365 } 366 updateLayoutParamsForCutout()367 private boolean updateLayoutParamsForCutout() { 368 if (mLayoutState == LAYOUT_CUTOUT) { 369 return false; 370 } 371 mLayoutState = LAYOUT_CUTOUT; 372 373 if (mCutoutSpace == null) { 374 updateLayoutParamsNoCutout(); 375 } 376 377 Rect bounds = new Rect(); 378 boundsFromDirection(mDisplayCutout, Gravity.TOP, bounds); 379 380 mCutoutSpace.setVisibility(View.VISIBLE); 381 RelativeLayout.LayoutParams lp = (LayoutParams) mCutoutSpace.getLayoutParams(); 382 bounds.left = bounds.left + mCutoutSideNudge; 383 bounds.right = bounds.right - mCutoutSideNudge; 384 lp.width = bounds.width(); 385 lp.height = bounds.height(); 386 lp.addRule(RelativeLayout.CENTER_IN_PARENT); 387 388 lp = (LayoutParams) mCarrierLabel.getLayoutParams(); 389 lp.addRule(RelativeLayout.START_OF, R.id.cutout_space_view); 390 updateCarrierLabelMargin(); 391 392 lp = (LayoutParams) mStatusIconArea.getLayoutParams(); 393 lp.addRule(RelativeLayout.RIGHT_OF, R.id.cutout_space_view); 394 lp.width = LayoutParams.MATCH_PARENT; 395 lp.setMarginStart(0); 396 return true; 397 } 398 399 /** Should only be called from {@link KeyguardStatusBarViewController}. */ onUserInfoChanged(Drawable picture)400 void onUserInfoChanged(Drawable picture) { 401 mMultiUserAvatar.setImageDrawable(picture); 402 } 403 404 /** 405 * Should only be called from {@link KeyguardStatusBarViewController} or 406 * {@link com.android.systemui.statusbar.ui.binder.KeyguardStatusBarViewBinder}. 407 */ onBatteryChargingChanged(boolean charging)408 public void onBatteryChargingChanged(boolean charging) { 409 if (mBatteryCharging != charging) { 410 mBatteryCharging = charging; 411 updateVisibilities(); 412 } 413 } 414 415 /** 416 * Should only be called from {@link KeyguardStatusBarViewController} or 417 * {@link com.android.systemui.statusbar.ui.binder.KeyguardStatusBarViewBinder}. 418 */ setKeyguardUserSwitcherEnabled(boolean enabled)419 public void setKeyguardUserSwitcherEnabled(boolean enabled) { 420 mKeyguardUserSwitcherEnabled = enabled; 421 } 422 setKeyguardUserAvatarEnabled(boolean enabled)423 void setKeyguardUserAvatarEnabled(boolean enabled) { 424 mKeyguardUserAvatarEnabled = enabled; 425 updateVisibilities(); 426 } 427 428 @VisibleForTesting isKeyguardUserAvatarEnabled()429 boolean isKeyguardUserAvatarEnabled() { 430 return mKeyguardUserAvatarEnabled; 431 } 432 433 @Override setVisibility(int visibility)434 public void setVisibility(int visibility) { 435 super.setVisibility(visibility); 436 if (visibility != View.VISIBLE) { 437 mSystemIconsContainer.animate().cancel(); 438 mSystemIconsContainer.setTranslationX(0); 439 mMultiUserAvatar.animate().cancel(); 440 mMultiUserAvatar.setAlpha(1f); 441 } else { 442 updateVisibilities(); 443 updateSystemIconsLayoutParams(); 444 } 445 } 446 447 @Override hasOverlappingRendering()448 public boolean hasOverlappingRendering() { 449 return false; 450 } 451 452 /** Should only be called from {@link KeyguardStatusBarViewController}. */ onThemeChanged(TintedIconManager iconManager)453 void onThemeChanged(TintedIconManager iconManager) { 454 if (mBatteryView != null) { 455 mBatteryView.setColorsFromContext(mContext); 456 } 457 updateIconsAndTextColors(iconManager); 458 } 459 460 /** Should only be called from {@link KeyguardStatusBarViewController}. */ onOverlayChanged()461 void onOverlayChanged() { 462 final int carrierTheme = R.style.TextAppearance_StatusBar_Default; 463 mCarrierLabel.setTextAppearance(carrierTheme); 464 if (mBatteryView != null) { 465 mBatteryView.updatePercentView(); 466 } 467 468 final int userSwitcherTheme = R.style.TextAppearance_StatusBar_UserChip; 469 TextView userSwitcherName = mUserSwitcherContainer.findViewById(R.id.current_user_name); 470 if (userSwitcherName != null) { 471 userSwitcherName.setTextAppearance(userSwitcherTheme); 472 } 473 } 474 updateIconsAndTextColors(TintedIconManager iconManager)475 private void updateIconsAndTextColors(TintedIconManager iconManager) { 476 @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext, 477 R.attr.wallpaperTextColor); 478 float luminance = Color.luminance(textColor); 479 @ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext, 480 luminance < 0.5 481 ? com.android.settingslib.R.color.dark_mode_icon_color_single_tone 482 : com.android.settingslib.R.color.light_mode_icon_color_single_tone); 483 @ColorInt int contrastColor = luminance < 0.5 484 ? DarkIconDispatcherImpl.DEFAULT_ICON_TINT 485 : DarkIconDispatcherImpl.DEFAULT_INVERSE_ICON_TINT; 486 float intensity = textColor == Color.WHITE ? 0 : 1; 487 mCarrierLabel.setTextColor(iconColor); 488 489 TextView userSwitcherName = mUserSwitcherContainer.findViewById(R.id.current_user_name); 490 if (userSwitcherName != null) { 491 userSwitcherName.setTextColor(Utils.getColorStateListDefaultColor( 492 mContext, 493 com.android.settingslib.R.color.light_mode_icon_color_single_tone)); 494 } 495 496 if (iconManager != null) { 497 iconManager.setTint(iconColor, contrastColor); 498 } 499 500 mDarkChange.setValue(new DarkChange(mEmptyTintRect, intensity, iconColor)); 501 applyDarkness(R.id.battery, mEmptyTintRect, intensity, iconColor); 502 applyDarkness(R.id.clock, mEmptyTintRect, intensity, iconColor); 503 } 504 505 private void applyDarkness(int id, ArrayList<Rect> tintAreas, float intensity, int color) { 506 View v = findViewById(id); 507 if (v instanceof DarkReceiver) { 508 ((DarkReceiver) v).onDarkChanged(tintAreas, intensity, color); 509 } 510 } 511 512 /** 513 * Calculates the margin that isn't already accounted for in the view's padding. 514 */ 515 private int calculateMargin(int margin, int padding) { 516 if (padding >= margin) { 517 return 0; 518 } else { 519 return margin - padding; 520 } 521 } 522 523 /** Should only be called from {@link KeyguardStatusBarViewController}. */ 524 void dump(PrintWriter pw, String[] args) { 525 pw.println("KeyguardStatusBarView:"); 526 pw.println(" mBatteryCharging: " + mBatteryCharging); 527 pw.println(" mLayoutState: " + mLayoutState); 528 pw.println(" mKeyguardUserSwitcherEnabled: " + mKeyguardUserSwitcherEnabled); 529 if (mBatteryView != null) { 530 mBatteryView.dump(pw, args); 531 } 532 } 533 534 @Override 535 protected void onLayout(boolean changed, int l, int t, int r, int b) { 536 super.onLayout(changed, l, t, r, b); 537 updateClipping(); 538 } 539 540 /** 541 * Set the clipping on the top of the view. 542 * 543 * Should only be called from {@link KeyguardStatusBarViewController}. 544 */ 545 void setTopClipping(int topClipping) { 546 if (topClipping != mTopClipping) { 547 mTopClipping = topClipping; 548 updateClipping(); 549 } 550 } 551 552 private void updateClipping() { 553 mClipRect.set(0, mTopClipping, getWidth(), getHeight()); 554 setClipBounds(mClipRect); 555 } 556 557 @Override 558 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 559 Trace.beginSection("KeyguardStatusBarView#onMeasure"); 560 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 561 Trace.endSection(); 562 } 563 564 public StateFlow<DarkChange> darkChangeFlow() { 565 return FlowKt.asStateFlow(mDarkChange); 566 } 567 } 568