1 /* 2 * Copyright (C) 2017 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.keyguard; 18 19 import static android.app.slice.Slice.HINT_LIST_ITEM; 20 import static android.view.Display.DEFAULT_DISPLAY; 21 import static android.view.Display.INVALID_DISPLAY; 22 23 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; 24 25 import android.animation.LayoutTransition; 26 import android.animation.ObjectAnimator; 27 import android.animation.PropertyValuesHolder; 28 import android.annotation.ColorInt; 29 import android.annotation.StyleRes; 30 import android.app.PendingIntent; 31 import android.content.Context; 32 import android.graphics.Color; 33 import android.graphics.drawable.Drawable; 34 import android.net.Uri; 35 import android.os.Trace; 36 import android.provider.Settings; 37 import android.text.TextUtils; 38 import android.text.TextUtils.TruncateAt; 39 import android.util.AttributeSet; 40 import android.util.Log; 41 import android.util.TypedValue; 42 import android.view.View; 43 import android.view.animation.Animation; 44 import android.widget.Button; 45 import android.widget.LinearLayout; 46 import android.widget.TextView; 47 48 import androidx.lifecycle.LiveData; 49 import androidx.lifecycle.Observer; 50 import androidx.slice.Slice; 51 import androidx.slice.SliceItem; 52 import androidx.slice.SliceViewManager; 53 import androidx.slice.core.SliceQuery; 54 import androidx.slice.widget.ListContent; 55 import androidx.slice.widget.RowContent; 56 import androidx.slice.widget.SliceContent; 57 import androidx.slice.widget.SliceLiveData; 58 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.internal.graphics.ColorUtils; 61 import com.android.settingslib.Utils; 62 import com.android.systemui.Dependency; 63 import com.android.systemui.Interpolators; 64 import com.android.systemui.R; 65 import com.android.systemui.keyguard.KeyguardSliceProvider; 66 import com.android.systemui.plugins.ActivityStarter; 67 import com.android.systemui.statusbar.policy.ConfigurationController; 68 import com.android.systemui.tuner.TunerService; 69 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener; 70 71 import java.io.FileDescriptor; 72 import java.io.PrintWriter; 73 import java.util.ArrayList; 74 import java.util.HashMap; 75 import java.util.List; 76 77 import javax.inject.Inject; 78 import javax.inject.Named; 79 80 /** 81 * View visible under the clock on the lock screen and AoD. 82 */ 83 public class KeyguardSliceView extends LinearLayout implements View.OnClickListener, 84 Observer<Slice>, TunerService.Tunable, ConfigurationController.ConfigurationListener { 85 86 private static final String TAG = "KeyguardSliceView"; 87 public static final int DEFAULT_ANIM_DURATION = 550; 88 89 private final HashMap<View, PendingIntent> mClickActions; 90 private final ActivityStarter mActivityStarter; 91 private final ConfigurationController mConfigurationController; 92 private final LayoutTransition mLayoutTransition; 93 private Uri mKeyguardSliceUri; 94 @VisibleForTesting 95 TextView mTitle; 96 private Row mRow; 97 private int mTextColor; 98 private float mDarkAmount = 0; 99 100 private LiveData<Slice> mLiveData; 101 private int mDisplayId = INVALID_DISPLAY; 102 private int mIconSize; 103 private int mIconSizeWithHeader; 104 /** 105 * Runnable called whenever the view contents change. 106 */ 107 private Runnable mContentChangeListener; 108 private Slice mSlice; 109 private boolean mHasHeader; 110 private final int mRowWithHeaderPadding; 111 private final int mRowPadding; 112 private float mRowTextSize; 113 private float mRowWithHeaderTextSize; 114 115 @Inject KeyguardSliceView(@amedVIEW_CONTEXT) Context context, AttributeSet attrs, ActivityStarter activityStarter, ConfigurationController configurationController)116 public KeyguardSliceView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, 117 ActivityStarter activityStarter, ConfigurationController configurationController) { 118 super(context, attrs); 119 120 TunerService tunerService = Dependency.get(TunerService.class); 121 tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI); 122 123 mClickActions = new HashMap<>(); 124 mRowPadding = context.getResources().getDimensionPixelSize(R.dimen.subtitle_clock_padding); 125 mRowWithHeaderPadding = context.getResources() 126 .getDimensionPixelSize(R.dimen.header_subtitle_padding); 127 mActivityStarter = activityStarter; 128 mConfigurationController = configurationController; 129 130 mLayoutTransition = new LayoutTransition(); 131 mLayoutTransition.setStagger(LayoutTransition.CHANGE_APPEARING, DEFAULT_ANIM_DURATION / 2); 132 mLayoutTransition.setDuration(LayoutTransition.APPEARING, DEFAULT_ANIM_DURATION); 133 mLayoutTransition.setDuration(LayoutTransition.DISAPPEARING, DEFAULT_ANIM_DURATION / 2); 134 mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING); 135 mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); 136 mLayoutTransition.setInterpolator(LayoutTransition.APPEARING, 137 Interpolators.FAST_OUT_SLOW_IN); 138 mLayoutTransition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT); 139 mLayoutTransition.setAnimateParentHierarchy(false); 140 } 141 142 @Override onFinishInflate()143 protected void onFinishInflate() { 144 super.onFinishInflate(); 145 mTitle = findViewById(R.id.title); 146 mRow = findViewById(R.id.row); 147 mTextColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); 148 mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size); 149 mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_size); 150 mRowTextSize = mContext.getResources().getDimensionPixelSize( 151 R.dimen.widget_label_font_size); 152 mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize( 153 R.dimen.header_row_font_size); 154 mTitle.setOnClickListener(this); 155 } 156 157 @Override onAttachedToWindow()158 protected void onAttachedToWindow() { 159 super.onAttachedToWindow(); 160 161 mDisplayId = getDisplay().getDisplayId(); 162 // Make sure we always have the most current slice 163 mLiveData.observeForever(this); 164 mConfigurationController.addCallback(this); 165 } 166 167 @Override onDetachedFromWindow()168 protected void onDetachedFromWindow() { 169 super.onDetachedFromWindow(); 170 171 // TODO(b/117344873) Remove below work around after this issue be fixed. 172 if (mDisplayId == DEFAULT_DISPLAY) { 173 mLiveData.removeObserver(this); 174 } 175 mConfigurationController.removeCallback(this); 176 } 177 178 @Override onVisibilityAggregated(boolean isVisible)179 public void onVisibilityAggregated(boolean isVisible) { 180 super.onVisibilityAggregated(isVisible); 181 setLayoutTransition(isVisible ? mLayoutTransition : null); 182 } 183 184 /** 185 * Returns whether the current visible slice has a title/header. 186 */ hasHeader()187 public boolean hasHeader() { 188 return mHasHeader; 189 } 190 showSlice()191 private void showSlice() { 192 Trace.beginSection("KeyguardSliceView#showSlice"); 193 if (mSlice == null) { 194 mTitle.setVisibility(GONE); 195 mRow.setVisibility(GONE); 196 mHasHeader = false; 197 if (mContentChangeListener != null) { 198 mContentChangeListener.run(); 199 } 200 Trace.endSection(); 201 return; 202 } 203 mClickActions.clear(); 204 205 ListContent lc = new ListContent(getContext(), mSlice); 206 SliceContent headerContent = lc.getHeader(); 207 mHasHeader = headerContent != null && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM); 208 List<SliceContent> subItems = new ArrayList<>(); 209 for (int i = 0; i < lc.getRowItems().size(); i++) { 210 SliceContent subItem = lc.getRowItems().get(i); 211 String itemUri = subItem.getSliceItem().getSlice().getUri().toString(); 212 // Filter out the action row 213 if (!KeyguardSliceProvider.KEYGUARD_ACTION_URI.equals(itemUri)) { 214 subItems.add(subItem); 215 } 216 } 217 if (!mHasHeader) { 218 mTitle.setVisibility(GONE); 219 } else { 220 mTitle.setVisibility(VISIBLE); 221 222 RowContent header = lc.getHeader(); 223 SliceItem mainTitle = header.getTitleItem(); 224 CharSequence title = mainTitle != null ? mainTitle.getText() : null; 225 mTitle.setText(title); 226 if (header.getPrimaryAction() != null 227 && header.getPrimaryAction().getAction() != null) { 228 mClickActions.put(mTitle, header.getPrimaryAction().getAction()); 229 } 230 } 231 232 final int subItemsCount = subItems.size(); 233 final int blendedColor = getTextColor(); 234 final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it 235 mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE); 236 LinearLayout.LayoutParams layoutParams = (LayoutParams) mRow.getLayoutParams(); 237 layoutParams.topMargin = mHasHeader ? mRowWithHeaderPadding : mRowPadding; 238 mRow.setLayoutParams(layoutParams); 239 240 for (int i = startIndex; i < subItemsCount; i++) { 241 RowContent rc = (RowContent) subItems.get(i); 242 SliceItem item = rc.getSliceItem(); 243 final Uri itemTag = item.getSlice().getUri(); 244 // Try to reuse the view if already exists in the layout 245 KeyguardSliceButton button = mRow.findViewWithTag(itemTag); 246 if (button == null) { 247 button = new KeyguardSliceButton(mContext); 248 button.setTextColor(blendedColor); 249 button.setTag(itemTag); 250 final int viewIndex = i - (mHasHeader ? 1 : 0); 251 mRow.addView(button, viewIndex); 252 } 253 254 PendingIntent pendingIntent = null; 255 if (rc.getPrimaryAction() != null) { 256 pendingIntent = rc.getPrimaryAction().getAction(); 257 } 258 mClickActions.put(button, pendingIntent); 259 260 final SliceItem titleItem = rc.getTitleItem(); 261 button.setText(titleItem == null ? null : titleItem.getText()); 262 button.setContentDescription(rc.getContentDescription()); 263 button.setTextSize(TypedValue.COMPLEX_UNIT_PX, 264 mHasHeader ? mRowWithHeaderTextSize : mRowTextSize); 265 266 Drawable iconDrawable = null; 267 SliceItem icon = SliceQuery.find(item.getSlice(), 268 android.app.slice.SliceItem.FORMAT_IMAGE); 269 if (icon != null) { 270 final int iconSize = mHasHeader ? mIconSizeWithHeader : mIconSize; 271 iconDrawable = icon.getIcon().loadDrawable(mContext); 272 if (iconDrawable != null) { 273 final int width = (int) (iconDrawable.getIntrinsicWidth() 274 / (float) iconDrawable.getIntrinsicHeight() * iconSize); 275 iconDrawable.setBounds(0, 0, Math.max(width, 1), iconSize); 276 } 277 } 278 button.setCompoundDrawables(iconDrawable, null, null, null); 279 button.setOnClickListener(this); 280 button.setClickable(pendingIntent != null); 281 } 282 283 // Removing old views 284 for (int i = 0; i < mRow.getChildCount(); i++) { 285 View child = mRow.getChildAt(i); 286 if (!mClickActions.containsKey(child)) { 287 mRow.removeView(child); 288 i--; 289 } 290 } 291 292 if (mContentChangeListener != null) { 293 mContentChangeListener.run(); 294 } 295 Trace.endSection(); 296 } 297 setDarkAmount(float darkAmount)298 public void setDarkAmount(float darkAmount) { 299 mDarkAmount = darkAmount; 300 mRow.setDarkAmount(darkAmount); 301 updateTextColors(); 302 } 303 updateTextColors()304 private void updateTextColors() { 305 final int blendedColor = getTextColor(); 306 mTitle.setTextColor(blendedColor); 307 int childCount = mRow.getChildCount(); 308 for (int i = 0; i < childCount; i++) { 309 View v = mRow.getChildAt(i); 310 if (v instanceof Button) { 311 ((Button) v).setTextColor(blendedColor); 312 } 313 } 314 } 315 316 @Override onClick(View v)317 public void onClick(View v) { 318 final PendingIntent action = mClickActions.get(v); 319 if (action != null) { 320 mActivityStarter.startPendingIntentDismissingKeyguard(action); 321 } 322 } 323 324 /** 325 * Runnable that gets invoked every time the title or the row visibility changes. 326 * @param contentChangeListener The listener. 327 */ setContentChangeListener(Runnable contentChangeListener)328 public void setContentChangeListener(Runnable contentChangeListener) { 329 mContentChangeListener = contentChangeListener; 330 } 331 332 /** 333 * LiveData observer lifecycle. 334 * @param slice the new slice content. 335 */ 336 @Override onChanged(Slice slice)337 public void onChanged(Slice slice) { 338 mSlice = slice; 339 showSlice(); 340 } 341 342 @Override onTuningChanged(String key, String newValue)343 public void onTuningChanged(String key, String newValue) { 344 setupUri(newValue); 345 } 346 347 /** 348 * Sets the slice provider Uri. 349 */ setupUri(String uriString)350 public void setupUri(String uriString) { 351 if (uriString == null) { 352 uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI; 353 } 354 355 boolean wasObserving = false; 356 if (mLiveData != null && mLiveData.hasActiveObservers()) { 357 wasObserving = true; 358 mLiveData.removeObserver(this); 359 } 360 361 mKeyguardSliceUri = Uri.parse(uriString); 362 mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri); 363 364 if (wasObserving) { 365 mLiveData.observeForever(this); 366 } 367 } 368 369 @VisibleForTesting getTextColor()370 int getTextColor() { 371 return ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); 372 } 373 374 @VisibleForTesting setTextColor(@olorInt int textColor)375 void setTextColor(@ColorInt int textColor) { 376 mTextColor = textColor; 377 updateTextColors(); 378 } 379 380 @Override onDensityOrFontScaleChanged()381 public void onDensityOrFontScaleChanged() { 382 mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.widget_icon_size); 383 mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_size); 384 mRowTextSize = mContext.getResources().getDimensionPixelSize( 385 R.dimen.widget_label_font_size); 386 mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize( 387 R.dimen.header_row_font_size); 388 } 389 refresh()390 public void refresh() { 391 Slice slice; 392 Trace.beginSection("KeyguardSliceView#refresh"); 393 // We can optimize performance and avoid binder calls when we know that we're bound 394 // to a Slice on the same process. 395 if (KeyguardSliceProvider.KEYGUARD_SLICE_URI.equals(mKeyguardSliceUri.toString())) { 396 KeyguardSliceProvider instance = KeyguardSliceProvider.getAttachedInstance(); 397 if (instance != null) { 398 slice = instance.onBindSlice(mKeyguardSliceUri); 399 } else { 400 Log.w(TAG, "Keyguard slice not bound yet?"); 401 slice = null; 402 } 403 } else { 404 slice = SliceViewManager.getInstance(getContext()).bindSlice(mKeyguardSliceUri); 405 } 406 onChanged(slice); 407 Trace.endSection(); 408 } 409 dump(FileDescriptor fd, PrintWriter pw, String[] args)410 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 411 pw.println("KeyguardSliceView:"); 412 pw.println(" mClickActions: " + mClickActions); 413 pw.println(" mTitle: " + (mTitle == null ? "null" : mTitle.getVisibility() == VISIBLE)); 414 pw.println(" mRow: " + (mRow == null ? "null" : mRow.getVisibility() == VISIBLE)); 415 pw.println(" mTextColor: " + Integer.toHexString(mTextColor)); 416 pw.println(" mDarkAmount: " + mDarkAmount); 417 pw.println(" mSlice: " + mSlice); 418 pw.println(" mHasHeader: " + mHasHeader); 419 } 420 421 public static class Row extends LinearLayout { 422 423 /** 424 * This view is visible in AOD, which means that the device will sleep if we 425 * don't hold a wake lock. We want to enter doze only after all views have reached 426 * their desired positions. 427 */ 428 private final Animation.AnimationListener mKeepAwakeListener; 429 private LayoutTransition mLayoutTransition; 430 private float mDarkAmount; 431 Row(Context context)432 public Row(Context context) { 433 this(context, null); 434 } 435 Row(Context context, AttributeSet attrs)436 public Row(Context context, AttributeSet attrs) { 437 this(context, attrs, 0); 438 } 439 Row(Context context, AttributeSet attrs, int defStyleAttr)440 public Row(Context context, AttributeSet attrs, int defStyleAttr) { 441 this(context, attrs, defStyleAttr, 0); 442 } 443 Row(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)444 public Row(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 445 super(context, attrs, defStyleAttr, defStyleRes); 446 mKeepAwakeListener = new KeepAwakeAnimationListener(mContext); 447 } 448 449 @Override onFinishInflate()450 protected void onFinishInflate() { 451 mLayoutTransition = new LayoutTransition(); 452 mLayoutTransition.setDuration(DEFAULT_ANIM_DURATION); 453 454 PropertyValuesHolder left = PropertyValuesHolder.ofInt("left", 0, 1); 455 PropertyValuesHolder right = PropertyValuesHolder.ofInt("right", 0, 1); 456 ObjectAnimator changeAnimator = ObjectAnimator.ofPropertyValuesHolder((Object) null, 457 left, right); 458 mLayoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, changeAnimator); 459 mLayoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changeAnimator); 460 mLayoutTransition.setInterpolator(LayoutTransition.CHANGE_APPEARING, 461 Interpolators.ACCELERATE_DECELERATE); 462 mLayoutTransition.setInterpolator(LayoutTransition.CHANGE_DISAPPEARING, 463 Interpolators.ACCELERATE_DECELERATE); 464 mLayoutTransition.setStartDelay(LayoutTransition.CHANGE_APPEARING, 465 DEFAULT_ANIM_DURATION); 466 mLayoutTransition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 467 DEFAULT_ANIM_DURATION); 468 469 ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f); 470 mLayoutTransition.setAnimator(LayoutTransition.APPEARING, appearAnimator); 471 mLayoutTransition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN); 472 473 ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f); 474 mLayoutTransition.setInterpolator(LayoutTransition.DISAPPEARING, 475 Interpolators.ALPHA_OUT); 476 mLayoutTransition.setDuration(LayoutTransition.DISAPPEARING, DEFAULT_ANIM_DURATION / 4); 477 mLayoutTransition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator); 478 479 mLayoutTransition.setAnimateParentHierarchy(false); 480 } 481 482 @Override onVisibilityAggregated(boolean isVisible)483 public void onVisibilityAggregated(boolean isVisible) { 484 super.onVisibilityAggregated(isVisible); 485 setLayoutTransition(isVisible ? mLayoutTransition : null); 486 } 487 488 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)489 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 490 int width = MeasureSpec.getSize(widthMeasureSpec); 491 int childCount = getChildCount(); 492 for (int i = 0; i < childCount; i++) { 493 View child = getChildAt(i); 494 if (child instanceof KeyguardSliceButton) { 495 ((KeyguardSliceButton) child).setMaxWidth(width / childCount); 496 } 497 } 498 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 499 } 500 setDarkAmount(float darkAmount)501 public void setDarkAmount(float darkAmount) { 502 boolean isAwake = darkAmount != 0; 503 boolean wasAwake = mDarkAmount != 0; 504 if (isAwake == wasAwake) { 505 return; 506 } 507 mDarkAmount = darkAmount; 508 setLayoutAnimationListener(isAwake ? null : mKeepAwakeListener); 509 } 510 511 @Override hasOverlappingRendering()512 public boolean hasOverlappingRendering() { 513 return false; 514 } 515 } 516 517 /** 518 * Representation of an item that appears under the clock on main keyguard message. 519 */ 520 @VisibleForTesting 521 static class KeyguardSliceButton extends Button implements 522 ConfigurationController.ConfigurationListener { 523 524 @StyleRes 525 private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary; 526 KeyguardSliceButton(Context context)527 public KeyguardSliceButton(Context context) { 528 super(context, null /* attrs */, 0 /* styleAttr */, sStyleId); 529 onDensityOrFontScaleChanged(); 530 setEllipsize(TruncateAt.END); 531 } 532 533 @Override onAttachedToWindow()534 protected void onAttachedToWindow() { 535 super.onAttachedToWindow(); 536 Dependency.get(ConfigurationController.class).addCallback(this); 537 } 538 539 @Override onDetachedFromWindow()540 protected void onDetachedFromWindow() { 541 super.onDetachedFromWindow(); 542 Dependency.get(ConfigurationController.class).removeCallback(this); 543 } 544 545 @Override onDensityOrFontScaleChanged()546 public void onDensityOrFontScaleChanged() { 547 updatePadding(); 548 } 549 550 @Override onOverlayChanged()551 public void onOverlayChanged() { 552 setTextAppearance(sStyleId); 553 } 554 555 @Override setText(CharSequence text, BufferType type)556 public void setText(CharSequence text, BufferType type) { 557 super.setText(text, type); 558 updatePadding(); 559 } 560 updatePadding()561 private void updatePadding() { 562 boolean hasText = !TextUtils.isEmpty(getText()); 563 int horizontalPadding = (int) getContext().getResources() 564 .getDimension(R.dimen.widget_horizontal_padding) / 2; 565 setPadding(horizontalPadding, 0, horizontalPadding * (hasText ? 1 : -1), 0); 566 setCompoundDrawablePadding((int) mContext.getResources() 567 .getDimension(R.dimen.widget_icon_padding)); 568 } 569 570 @Override setTextColor(int color)571 public void setTextColor(int color) { 572 super.setTextColor(color); 573 updateDrawableColors(); 574 } 575 576 @Override setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)577 public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, 578 Drawable bottom) { 579 super.setCompoundDrawables(left, top, right, bottom); 580 updateDrawableColors(); 581 updatePadding(); 582 } 583 updateDrawableColors()584 private void updateDrawableColors() { 585 final int color = getCurrentTextColor(); 586 for (Drawable drawable : getCompoundDrawables()) { 587 if (drawable != null) { 588 drawable.setTint(color); 589 } 590 } 591 } 592 } 593 } 594