1 /* 2 * Copyright (C) 2020 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.media.dialog; 18 19 import static com.android.systemui.media.dialog.MediaOutputSeekbar.VOLUME_PERCENTAGE_SCALE_SIZE; 20 21 import android.animation.Animator; 22 import android.animation.ValueAnimator; 23 import android.annotation.DrawableRes; 24 import android.app.WallpaperColors; 25 import android.content.Context; 26 import android.content.res.ColorStateList; 27 import android.graphics.Typeface; 28 import android.graphics.drawable.ClipDrawable; 29 import android.graphics.drawable.Drawable; 30 import android.graphics.drawable.GradientDrawable; 31 import android.graphics.drawable.Icon; 32 import android.graphics.drawable.LayerDrawable; 33 import android.text.TextUtils; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.view.animation.LinearInterpolator; 38 import android.widget.CheckBox; 39 import android.widget.FrameLayout; 40 import android.widget.ImageView; 41 import android.widget.LinearLayout; 42 import android.widget.ProgressBar; 43 import android.widget.SeekBar; 44 import android.widget.TextView; 45 46 import androidx.annotation.NonNull; 47 import androidx.annotation.VisibleForTesting; 48 import androidx.recyclerview.widget.RecyclerView; 49 50 import com.android.settingslib.media.MediaDevice; 51 import com.android.settingslib.utils.ThreadUtils; 52 import com.android.systemui.res.R; 53 54 import java.util.List; 55 56 /** 57 * Base adapter for media output dialog. 58 */ 59 public abstract class MediaOutputBaseAdapter extends 60 RecyclerView.Adapter<RecyclerView.ViewHolder> { 61 62 static final int CUSTOMIZED_ITEM_PAIR_NEW = 1; 63 static final int CUSTOMIZED_ITEM_GROUP = 2; 64 static final int CUSTOMIZED_ITEM_DYNAMIC_GROUP = 3; 65 66 protected final MediaOutputController mController; 67 68 private static final int UNMUTE_DEFAULT_VOLUME = 2; 69 70 Context mContext; 71 View mHolderView; 72 boolean mIsDragging; 73 int mCurrentActivePosition; 74 private boolean mIsInitVolumeFirstTime; 75 MediaOutputBaseAdapter(MediaOutputController controller)76 public MediaOutputBaseAdapter(MediaOutputController controller) { 77 mController = controller; 78 mIsDragging = false; 79 mCurrentActivePosition = -1; 80 mIsInitVolumeFirstTime = true; 81 } 82 83 /** 84 * Refresh current dataset 85 */ updateItems()86 public abstract void updateItems(); 87 88 @Override onCreateViewHolder(@onNull ViewGroup viewGroup, int viewType)89 public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, 90 int viewType) { 91 mContext = viewGroup.getContext(); 92 mHolderView = LayoutInflater.from(mContext).inflate(MediaItem.getMediaLayoutId(viewType), 93 viewGroup, false); 94 95 return null; 96 } 97 updateColorScheme(WallpaperColors wallpaperColors, boolean isDarkTheme)98 void updateColorScheme(WallpaperColors wallpaperColors, boolean isDarkTheme) { 99 mController.setCurrentColorScheme(wallpaperColors, isDarkTheme); 100 } 101 getItemTitle(MediaDevice device)102 CharSequence getItemTitle(MediaDevice device) { 103 return device.getName(); 104 } 105 isCurrentlyConnected(MediaDevice device)106 boolean isCurrentlyConnected(MediaDevice device) { 107 return TextUtils.equals(device.getId(), 108 mController.getCurrentConnectedMediaDevice().getId()) 109 || (mController.getSelectedMediaDevice().size() == 1 110 && isDeviceIncluded(mController.getSelectedMediaDevice(), device)); 111 } 112 isDeviceIncluded(List<MediaDevice> deviceList, MediaDevice targetDevice)113 boolean isDeviceIncluded(List<MediaDevice> deviceList, MediaDevice targetDevice) { 114 for (MediaDevice device : deviceList) { 115 if (TextUtils.equals(device.getId(), targetDevice.getId())) { 116 return true; 117 } 118 } 119 return false; 120 } 121 isDragging()122 boolean isDragging() { 123 return mIsDragging; 124 } 125 getCurrentActivePosition()126 int getCurrentActivePosition() { 127 return mCurrentActivePosition; 128 } 129 getController()130 public MediaOutputController getController() { 131 return mController; 132 } 133 134 /** 135 * ViewHolder for binding device view. 136 */ 137 abstract class MediaDeviceBaseViewHolder extends RecyclerView.ViewHolder { 138 139 private static final int ANIM_DURATION = 500; 140 141 final ViewGroup mContainerLayout; 142 final FrameLayout mItemLayout; 143 final FrameLayout mIconAreaLayout; 144 final TextView mTitleText; 145 final TextView mTwoLineTitleText; 146 final TextView mSubTitleText; 147 final TextView mVolumeValueText; 148 final ImageView mTitleIcon; 149 final ProgressBar mProgressBar; 150 final LinearLayout mTwoLineLayout; 151 final ImageView mStatusIcon; 152 final CheckBox mCheckBox; 153 final ViewGroup mEndTouchArea; 154 final ImageView mEndClickIcon; 155 @VisibleForTesting 156 MediaOutputSeekbar mSeekBar; 157 private String mDeviceId; 158 private ValueAnimator mCornerAnimator; 159 private ValueAnimator mVolumeAnimator; 160 private int mLatestUpdateVolume = -1; 161 MediaDeviceBaseViewHolder(View view)162 MediaDeviceBaseViewHolder(View view) { 163 super(view); 164 mContainerLayout = view.requireViewById(R.id.device_container); 165 mItemLayout = view.requireViewById(R.id.item_layout); 166 mTitleText = view.requireViewById(R.id.title); 167 mSubTitleText = view.requireViewById(R.id.subtitle); 168 mTwoLineLayout = view.requireViewById(R.id.two_line_layout); 169 mTwoLineTitleText = view.requireViewById(R.id.two_line_title); 170 mTitleIcon = view.requireViewById(R.id.title_icon); 171 mProgressBar = view.requireViewById(R.id.volume_indeterminate_progress); 172 mSeekBar = view.requireViewById(R.id.volume_seekbar); 173 mStatusIcon = view.requireViewById(R.id.media_output_item_status); 174 mCheckBox = view.requireViewById(R.id.check_box); 175 mEndTouchArea = view.requireViewById(R.id.end_action_area); 176 mEndClickIcon = view.requireViewById(R.id.media_output_item_end_click_icon); 177 mVolumeValueText = view.requireViewById(R.id.volume_value); 178 mIconAreaLayout = view.requireViewById(R.id.icon_area); 179 initAnimator(); 180 } 181 onBind(MediaDevice device, int position)182 void onBind(MediaDevice device, int position) { 183 mDeviceId = device.getId(); 184 mCheckBox.setVisibility(View.GONE); 185 mStatusIcon.setVisibility(View.GONE); 186 mEndTouchArea.setVisibility(View.GONE); 187 mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 188 mContainerLayout.setOnClickListener(null); 189 mContainerLayout.setContentDescription(null); 190 mTitleText.setTextColor(mController.getColorItemContent()); 191 mSubTitleText.setTextColor(mController.getColorItemContent()); 192 mSubTitleText.setSelected(true); 193 mTwoLineTitleText.setTextColor(mController.getColorItemContent()); 194 mVolumeValueText.setTextColor(mController.getColorItemContent()); 195 mSeekBar.setProgressTintList( 196 ColorStateList.valueOf(mController.getColorSeekbarProgress())); 197 } 198 onBind(int customizedItem)199 abstract void onBind(int customizedItem); 200 setSingleLineLayout(CharSequence title)201 void setSingleLineLayout(CharSequence title) { 202 setSingleLineLayout(title, false, false, false, false); 203 } 204 setSingleLineLayout(CharSequence title, boolean showSeekBar, boolean showProgressBar, boolean showCheckBox, boolean showEndTouchArea)205 void setSingleLineLayout(CharSequence title, boolean showSeekBar, 206 boolean showProgressBar, boolean showCheckBox, boolean showEndTouchArea) { 207 mTwoLineLayout.setVisibility(View.GONE); 208 boolean isActive = showSeekBar || showProgressBar; 209 if (!mCornerAnimator.isRunning()) { 210 final Drawable backgroundDrawable = 211 showSeekBar 212 ? mContext.getDrawable( 213 R.drawable.media_output_item_background_active) 214 .mutate() : mContext.getDrawable( 215 R.drawable.media_output_item_background) 216 .mutate(); 217 mItemLayout.setBackground(backgroundDrawable); 218 if (showSeekBar) { 219 updateSeekbarProgressBackground(); 220 } 221 } 222 mItemLayout.setBackgroundTintList( 223 ColorStateList.valueOf(isActive ? mController.getColorConnectedItemBackground() 224 : mController.getColorItemBackground())); 225 mIconAreaLayout.setBackgroundTintList( 226 ColorStateList.valueOf(showSeekBar ? mController.getColorSeekbarProgress() 227 : showProgressBar ? mController.getColorConnectedItemBackground() 228 : mController.getColorItemBackground())); 229 mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); 230 mSeekBar.setAlpha(1); 231 mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); 232 if (!showSeekBar) { 233 mSeekBar.resetVolume(); 234 } 235 mTitleText.setText(title); 236 mTitleText.setVisibility(View.VISIBLE); 237 mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE); 238 mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE); 239 ViewGroup.MarginLayoutParams params = 240 (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams(); 241 params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable() 242 : mController.getItemMarginEndDefault(); 243 mTitleIcon.setBackgroundTintList( 244 ColorStateList.valueOf(mController.getColorItemContent())); 245 } 246 setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar, boolean showProgressBar, boolean showSubtitle, boolean showStatus, boolean isFakeActive)247 void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar, 248 boolean showProgressBar, boolean showSubtitle, boolean showStatus, 249 boolean isFakeActive) { 250 setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle, 251 showStatus, false, isFakeActive); 252 } 253 setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused, boolean showSeekBar, boolean showProgressBar, boolean showSubtitle, boolean showStatus , boolean showEndTouchArea, boolean isFakeActive)254 void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused, 255 boolean showSeekBar, boolean showProgressBar, boolean showSubtitle, 256 boolean showStatus , boolean showEndTouchArea, boolean isFakeActive) { 257 mTitleText.setVisibility(View.GONE); 258 mTwoLineLayout.setVisibility(View.VISIBLE); 259 mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE); 260 mSeekBar.setAlpha(1); 261 mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); 262 final Drawable backgroundDrawable; 263 backgroundDrawable = mContext.getDrawable( 264 showSeekBar || isFakeActive ? R.drawable.media_output_item_background_active 265 : R.drawable.media_output_item_background).mutate(); 266 mItemLayout.setBackgroundTintList(ColorStateList.valueOf( 267 showSeekBar || isFakeActive ? mController.getColorConnectedItemBackground() 268 : mController.getColorItemBackground() 269 )); 270 mIconAreaLayout.setBackgroundTintList( 271 ColorStateList.valueOf(showProgressBar || isFakeActive 272 ? mController.getColorConnectedItemBackground() 273 : showSeekBar ? mController.getColorSeekbarProgress() 274 : mController.getColorItemBackground())); 275 if (showSeekBar) { 276 updateSeekbarProgressBackground(); 277 } 278 //update end click area by isActive 279 mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE); 280 mEndClickIcon.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE); 281 ViewGroup.MarginLayoutParams params = 282 (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams(); 283 params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable() 284 : mController.getItemMarginEndDefault(); 285 mItemLayout.setBackground(backgroundDrawable); 286 mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); 287 mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE); 288 mTwoLineTitleText.setTranslationY(0); 289 mTwoLineTitleText.setText(device == null ? title : getItemTitle(device)); 290 mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString( 291 bFocused ? com.android.internal.R.string.config_headlineFontFamilyMedium 292 : com.android.internal.R.string.config_headlineFontFamily), 293 Typeface.NORMAL)); 294 } 295 updateSeekbarProgressBackground()296 void updateSeekbarProgressBackground() { 297 final ClipDrawable clipDrawable = 298 (ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable()) 299 .findDrawableByLayerId(android.R.id.progress); 300 final GradientDrawable progressDrawable = 301 (GradientDrawable) clipDrawable.getDrawable(); 302 progressDrawable.setCornerRadii( 303 new float[]{0, 0, mController.getActiveRadius(), 304 mController.getActiveRadius(), 305 mController.getActiveRadius(), 306 mController.getActiveRadius(), 0, 0}); 307 } 308 initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible)309 void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) { 310 if (!mController.isVolumeControlEnabled(device)) { 311 disableSeekBar(); 312 } else { 313 enableSeekBar(device); 314 } 315 mSeekBar.setMaxVolume(device.getMaxVolume()); 316 final int currentVolume = device.getCurrentVolume(); 317 if (!mIsDragging) { 318 if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1 319 || currentVolume == mLatestUpdateVolume)) { 320 // Update only if volume of device and value of volume bar doesn't match. 321 // Check if response volume match with the latest request, to ignore obsolete 322 // response 323 if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) { 324 updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off 325 : R.drawable.media_output_icon_volume, 326 mController.getColorItemContent()); 327 } else { 328 if (!mVolumeAnimator.isStarted()) { 329 int percentage = 330 (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE 331 / (double) mSeekBar.getMax()); 332 if (percentage == 0) { 333 updateMutedVolumeIcon(); 334 } else { 335 updateUnmutedVolumeIcon(); 336 } 337 mSeekBar.setVolume(currentVolume); 338 mLatestUpdateVolume = -1; 339 } 340 } 341 } else if (currentVolume == 0) { 342 mSeekBar.resetVolume(); 343 updateMutedVolumeIcon(); 344 } 345 if (currentVolume == mLatestUpdateVolume) { 346 mLatestUpdateVolume = -1; 347 } 348 } 349 if (mIsInitVolumeFirstTime) { 350 mIsInitVolumeFirstTime = false; 351 } 352 mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 353 boolean mStartFromMute = false; 354 @Override 355 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 356 if (device == null || !fromUser) { 357 return; 358 } 359 int progressToVolume = MediaOutputSeekbar.scaleProgressToVolume(progress); 360 int deviceVolume = device.getCurrentVolume(); 361 int percentage = 362 (int) ((double) progressToVolume * VOLUME_PERCENTAGE_SCALE_SIZE 363 / (double) seekBar.getMax()); 364 mVolumeValueText.setText(mContext.getResources().getString( 365 R.string.media_output_dialog_volume_percentage, percentage)); 366 mVolumeValueText.setVisibility(View.VISIBLE); 367 if (mStartFromMute) { 368 updateUnmutedVolumeIcon(); 369 mStartFromMute = false; 370 } 371 if (progressToVolume != deviceVolume) { 372 mLatestUpdateVolume = progressToVolume; 373 mController.adjustVolume(device, progressToVolume); 374 } 375 } 376 377 @Override 378 public void onStartTrackingTouch(SeekBar seekBar) { 379 mTitleIcon.setVisibility(View.INVISIBLE); 380 mVolumeValueText.setVisibility(View.VISIBLE); 381 int currentVolume = MediaOutputSeekbar.scaleProgressToVolume( 382 seekBar.getProgress()); 383 mStartFromMute = (currentVolume == 0); 384 mIsDragging = true; 385 } 386 387 @Override 388 public void onStopTrackingTouch(SeekBar seekBar) { 389 int currentVolume = MediaOutputSeekbar.scaleProgressToVolume( 390 seekBar.getProgress()); 391 if (currentVolume == 0) { 392 seekBar.setProgress(0); 393 updateMutedVolumeIcon(); 394 } else { 395 updateUnmutedVolumeIcon(); 396 } 397 mTitleIcon.setVisibility(View.VISIBLE); 398 mVolumeValueText.setVisibility(View.GONE); 399 mController.logInteractionAdjustVolume(device); 400 mIsDragging = false; 401 } 402 }); 403 } 404 updateMutedVolumeIcon()405 void updateMutedVolumeIcon() { 406 mIconAreaLayout.setBackground( 407 mContext.getDrawable(R.drawable.media_output_item_background_active)); 408 updateTitleIcon(R.drawable.media_output_icon_volume_off, 409 mController.getColorItemContent()); 410 } 411 updateUnmutedVolumeIcon()412 void updateUnmutedVolumeIcon() { 413 mIconAreaLayout.setBackground( 414 mContext.getDrawable(R.drawable.media_output_title_icon_area) 415 ); 416 updateTitleIcon(R.drawable.media_output_icon_volume, 417 mController.getColorItemContent()); 418 } 419 updateTitleIcon(@rawableRes int id, int color)420 void updateTitleIcon(@DrawableRes int id, int color) { 421 mTitleIcon.setImageDrawable(mContext.getDrawable(id)); 422 mTitleIcon.setImageTintList(ColorStateList.valueOf(color)); 423 mIconAreaLayout.setBackgroundTintList( 424 ColorStateList.valueOf(mController.getColorSeekbarProgress())); 425 } 426 updateIconAreaClickListener(View.OnClickListener listener)427 void updateIconAreaClickListener(View.OnClickListener listener) { 428 mIconAreaLayout.setOnClickListener(listener); 429 } 430 initFakeActiveDevice()431 void initFakeActiveDevice() { 432 disableSeekBar(); 433 updateTitleIcon(R.drawable.media_output_icon_volume, 434 mController.getColorItemContent()); 435 final Drawable backgroundDrawable = mContext.getDrawable( 436 R.drawable.media_output_item_background_active) 437 .mutate(); 438 mItemLayout.setBackground(backgroundDrawable); 439 mItemLayout.setBackgroundTintList( 440 ColorStateList.valueOf(mController.getColorConnectedItemBackground())); 441 mIconAreaLayout.setBackgroundTintList( 442 ColorStateList.valueOf(mController.getColorConnectedItemBackground())); 443 } 444 animateCornerAndVolume(int fromProgress, int toProgress)445 private void animateCornerAndVolume(int fromProgress, int toProgress) { 446 final GradientDrawable layoutBackgroundDrawable = 447 (GradientDrawable) mItemLayout.getBackground(); 448 final ClipDrawable clipDrawable = 449 (ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable()) 450 .findDrawableByLayerId(android.R.id.progress); 451 final GradientDrawable targetBackgroundDrawable = 452 (GradientDrawable) (mIconAreaLayout.getBackground()); 453 mCornerAnimator.addUpdateListener(animation -> { 454 float value = (float) animation.getAnimatedValue(); 455 layoutBackgroundDrawable.setCornerRadius(value); 456 if (toProgress == 0) { 457 targetBackgroundDrawable.setCornerRadius(value); 458 } else { 459 targetBackgroundDrawable.setCornerRadii(new float[]{ 460 value, 461 value, 462 0, 0, 0, 0, value, value 463 }); 464 } 465 }); 466 mVolumeAnimator.setIntValues(fromProgress, toProgress); 467 mVolumeAnimator.start(); 468 mCornerAnimator.start(); 469 } 470 initAnimator()471 private void initAnimator() { 472 mCornerAnimator = ValueAnimator.ofFloat(mController.getInactiveRadius(), 473 mController.getActiveRadius()); 474 mCornerAnimator.setDuration(ANIM_DURATION); 475 mCornerAnimator.setInterpolator(new LinearInterpolator()); 476 477 mVolumeAnimator = ValueAnimator.ofInt(); 478 mVolumeAnimator.addUpdateListener(animation -> { 479 int value = (int) animation.getAnimatedValue(); 480 mSeekBar.setProgress(value); 481 }); 482 mVolumeAnimator.setDuration(ANIM_DURATION); 483 mVolumeAnimator.setInterpolator(new LinearInterpolator()); 484 mVolumeAnimator.addListener(new Animator.AnimatorListener() { 485 @Override 486 public void onAnimationStart(Animator animation) { 487 mSeekBar.setEnabled(false); 488 } 489 490 @Override 491 public void onAnimationEnd(Animator animation) { 492 mSeekBar.setEnabled(true); 493 } 494 495 @Override 496 public void onAnimationCancel(Animator animation) { 497 mSeekBar.setEnabled(true); 498 } 499 500 @Override 501 public void onAnimationRepeat(Animator animation) { 502 503 } 504 }); 505 } 506 disableSeekBar()507 protected void disableSeekBar() { 508 mSeekBar.setEnabled(false); 509 mSeekBar.setOnTouchListener((v, event) -> true); 510 updateIconAreaClickListener(null); 511 } 512 enableSeekBar(MediaDevice device)513 private void enableSeekBar(MediaDevice device) { 514 mSeekBar.setEnabled(true); 515 mSeekBar.setOnTouchListener((v, event) -> false); 516 updateIconAreaClickListener((v) -> { 517 if (device.getCurrentVolume() == 0) { 518 mController.logInteractionUnmuteDevice(device); 519 mSeekBar.setVolume(UNMUTE_DEFAULT_VOLUME); 520 mController.adjustVolume(device, UNMUTE_DEFAULT_VOLUME); 521 updateUnmutedVolumeIcon(); 522 mIconAreaLayout.setOnTouchListener(((iconV, event) -> false)); 523 } else { 524 mController.logInteractionMuteDevice(device); 525 mSeekBar.resetVolume(); 526 mController.adjustVolume(device, 0); 527 updateMutedVolumeIcon(); 528 mIconAreaLayout.setOnTouchListener(((iconV, event) -> { 529 mSeekBar.dispatchTouchEvent(event); 530 return false; 531 })); 532 } 533 }); 534 } 535 setUpDeviceIcon(MediaDevice device)536 protected void setUpDeviceIcon(MediaDevice device) { 537 ThreadUtils.postOnBackgroundThread(() -> { 538 Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext); 539 ThreadUtils.postOnMainThread(() -> { 540 if (!TextUtils.equals(mDeviceId, device.getId())) { 541 return; 542 } 543 mTitleIcon.setImageIcon(icon); 544 mTitleIcon.setImageTintList( 545 ColorStateList.valueOf(mController.getColorItemContent())); 546 }); 547 }); 548 } 549 } 550 } 551