1 /* 2 * Copyright (C) 2022 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.permissioncontroller.safetycenter.ui; 18 19 import static android.os.Build.VERSION_CODES.TIRAMISU; 20 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 21 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 22 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 23 24 import static java.util.Objects.requireNonNull; 25 26 import android.app.AlertDialog; 27 import android.app.Dialog; 28 import android.content.Context; 29 import android.os.Bundle; 30 import android.safetycenter.SafetyCenterIssue; 31 import android.text.TextUtils; 32 import android.util.Log; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.view.ViewGroup.MarginLayoutParams; 36 import android.widget.Button; 37 import android.widget.LinearLayout; 38 import android.widget.Space; 39 import android.widget.TextView; 40 41 import androidx.annotation.ColorRes; 42 import androidx.annotation.Nullable; 43 import androidx.annotation.RequiresApi; 44 import androidx.appcompat.view.ContextThemeWrapper; 45 import androidx.core.content.ContextCompat; 46 import androidx.fragment.app.DialogFragment; 47 import androidx.fragment.app.FragmentManager; 48 import androidx.preference.Preference; 49 import androidx.preference.PreferenceViewHolder; 50 51 import com.android.modules.utils.build.SdkLevel; 52 import com.android.permissioncontroller.R; 53 import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel; 54 55 import com.google.android.material.button.MaterialButton; 56 import com.google.android.material.shape.AbsoluteCornerSize; 57 import com.google.android.material.shape.CornerSize; 58 import com.google.android.material.shape.ShapeAppearanceModel; 59 60 import java.util.Objects; 61 62 /** A preference that displays a card representing a {@link SafetyCenterIssue}. */ 63 @RequiresApi(TIRAMISU) 64 public class IssueCardPreference extends Preference implements ComparablePreference { 65 66 public static final String TAG = IssueCardPreference.class.getSimpleName(); 67 68 private final IssueCardAnimator mIssueCardAnimator = 69 new IssueCardAnimator(this::markIssueResolvedUiCompleted); 70 private final SafetyCenterViewModel mSafetyCenterViewModel; 71 private final SafetyCenterIssue mIssue; 72 private final FragmentManager mDialogFragmentManager; 73 @Nullable private String mResolvedIssueActionId; 74 @Nullable private final Integer mTaskId; 75 private final boolean mIsDismissed; 76 private final PositionInCardList mPositionInCardList; 77 IssueCardPreference( Context context, SafetyCenterViewModel safetyCenterViewModel, SafetyCenterIssue issue, @Nullable String resolvedIssueActionId, FragmentManager dialogFragmentManager, @Nullable Integer launchTaskId, boolean isDismissed, PositionInCardList positionInCardList)78 public IssueCardPreference( 79 Context context, 80 SafetyCenterViewModel safetyCenterViewModel, 81 SafetyCenterIssue issue, 82 @Nullable String resolvedIssueActionId, 83 FragmentManager dialogFragmentManager, 84 @Nullable Integer launchTaskId, 85 boolean isDismissed, 86 PositionInCardList positionInCardList) { 87 super(context); 88 setLayoutResource(R.layout.preference_issue_card); 89 90 mSafetyCenterViewModel = requireNonNull(safetyCenterViewModel); 91 mIssue = requireNonNull(issue); 92 mDialogFragmentManager = dialogFragmentManager; 93 mResolvedIssueActionId = resolvedIssueActionId; 94 mTaskId = launchTaskId; 95 mIsDismissed = isDismissed; 96 mPositionInCardList = positionInCardList; 97 } 98 99 @Override onBindViewHolder(PreferenceViewHolder holder)100 public void onBindViewHolder(PreferenceViewHolder holder) { 101 super.onBindViewHolder(holder); 102 103 holder.itemView.setBackgroundResource(mPositionInCardList.getBackgroundDrawableResId()); 104 int topMargin = getTopMargin(mPositionInCardList, getContext()); 105 MarginLayoutParams layoutParams = (MarginLayoutParams) holder.itemView.getLayoutParams(); 106 if (layoutParams.topMargin != topMargin) { 107 layoutParams.topMargin = topMargin; 108 holder.itemView.setLayoutParams(layoutParams); 109 } 110 111 // Set default group visibility in case view is being reused 112 holder.findViewById(R.id.default_issue_content).setVisibility(View.VISIBLE); 113 holder.findViewById(R.id.resolved_issue_content).setVisibility(View.GONE); 114 115 configureDismissButton(holder.findViewById(R.id.issue_card_dismiss_btn)); 116 117 TextView titleTextView = (TextView) holder.findViewById(R.id.issue_card_title); 118 titleTextView.setText(mIssue.getTitle()); 119 ((TextView) holder.findViewById(R.id.issue_card_summary)).setText(mIssue.getSummary()); 120 121 TextView attributionTitleTextView = 122 (TextView) holder.findViewById(R.id.issue_card_attribution_title); 123 maybeDisplayText( 124 SdkLevel.isAtLeastU() ? mIssue.getAttributionTitle() : null, 125 attributionTitleTextView); 126 127 TextView subtitleTextView = (TextView) holder.findViewById(R.id.issue_card_subtitle); 128 maybeDisplayText(mIssue.getSubtitle(), subtitleTextView); 129 130 holder.itemView.setClickable(false); 131 132 configureContentDescription(attributionTitleTextView, titleTextView); 133 configureButtonList(holder); 134 configureSafetyProtectionView(holder); 135 maybeStartResolutionAnimation(holder); 136 137 mSafetyCenterViewModel 138 .getInteractionLogger() 139 .recordIssueViewed(mIssue, mIsDismissed); 140 } 141 maybeDisplayText(@ullable CharSequence maybeText, TextView textView)142 private void maybeDisplayText(@Nullable CharSequence maybeText, TextView textView) { 143 if (TextUtils.isEmpty(maybeText)) { 144 textView.setVisibility(View.GONE); 145 } else { 146 textView.setText(maybeText); 147 textView.setVisibility(View.VISIBLE); 148 } 149 } 150 configureContentDescription( TextView attributionTitleTextView, TextView titleTextView)151 private void configureContentDescription( 152 TextView attributionTitleTextView, TextView titleTextView) { 153 TextView firstVisibleTextView; 154 if (attributionTitleTextView.getVisibility() == View.VISIBLE) { 155 // Attribution title might not be present for an issue, title always is. 156 firstVisibleTextView = attributionTitleTextView; 157 158 // Clear the modified title description in case this view is reused. 159 titleTextView.setContentDescription(null); 160 } else { 161 firstVisibleTextView = titleTextView; 162 } 163 164 // We would like to say "alert" before reading the content of the issue card. Best way to 165 // do that is by modifying the content description of the first view that would be read 166 // in the issue card. 167 firstVisibleTextView.setContentDescription( 168 getContext() 169 .getString( 170 R.string.safety_center_issue_card_prefix_content_description, 171 firstVisibleTextView.getText())); 172 } 173 configureButtonList(PreferenceViewHolder holder)174 private void configureButtonList(PreferenceViewHolder holder) { 175 LinearLayout buttonList = 176 ((LinearLayout) holder.findViewById(R.id.issue_card_action_button_list)); 177 buttonList.removeAllViews(); // This view may be recycled from another issue 178 179 for (int i = 0; i < mIssue.getActions().size(); i++) { 180 SafetyCenterIssue.Action action = mIssue.getActions().get(i); 181 ActionButtonBuilder builder = 182 new ActionButtonBuilder(action, holder.itemView.getContext()) 183 .setIndex(i) 184 .setActionButtonListSize(mIssue.getActions().size()) 185 .setIsDismissed(mIsDismissed) 186 .setIsLargeScreen(buttonList instanceof EqualWidthContainer); 187 builder.buildAndAddToView(buttonList); 188 } 189 } 190 getTopMargin(PositionInCardList position, Context context)191 private int getTopMargin(PositionInCardList position, Context context) { 192 switch (position) { 193 case LIST_START_END: 194 case LIST_START_CARD_END: 195 return context.getResources() 196 .getDimensionPixelSize( 197 mIsDismissed ? R.dimen.sc_card_margin : R.dimen.sc_spacing_large); 198 default: 199 return position.getTopMargin(context); 200 } 201 } 202 configureSafetyProtectionView(PreferenceViewHolder holder)203 private void configureSafetyProtectionView(PreferenceViewHolder holder) { 204 View safetyProtectionSectionView = 205 holder.findViewById(R.id.issue_card_protected_by_android); 206 if (safetyProtectionSectionView.getVisibility() == View.GONE) { 207 holder.itemView.setPaddingRelative( 208 holder.itemView.getPaddingStart(), 209 holder.itemView.getPaddingTop(), 210 holder.itemView.getPaddingEnd(), 211 /* bottom= */ getContext() 212 .getResources() 213 .getDimensionPixelSize(R.dimen.sc_card_margin_bottom)); 214 } else { 215 holder.itemView.setPaddingRelative( 216 holder.itemView.getPaddingStart(), 217 holder.itemView.getPaddingTop(), 218 holder.itemView.getPaddingEnd(), 219 /* bottom= */ 0); 220 } 221 } 222 maybeStartResolutionAnimation(PreferenceViewHolder holder)223 private void maybeStartResolutionAnimation(PreferenceViewHolder holder) { 224 if (mResolvedIssueActionId == null) { 225 return; 226 } 227 228 for (SafetyCenterIssue.Action action : mIssue.getActions()) { 229 if (action.getId().equals(mResolvedIssueActionId)) { 230 mIssueCardAnimator.transitionToIssueResolvedThenMarkComplete( 231 getContext(), holder, action); 232 } 233 } 234 } 235 getSeverityLevel()236 public int getSeverityLevel() { 237 return mIssue.getSeverityLevel(); 238 } 239 configureDismissButton(View dismissButton)240 private void configureDismissButton(View dismissButton) { 241 if (mIssue.isDismissible() && !mIsDismissed) { 242 dismissButton.setOnClickListener( 243 mIssue.shouldConfirmDismissal() 244 ? new ConfirmDismissalOnClickListener() 245 : new DismissOnClickListener()); 246 dismissButton.setVisibility(View.VISIBLE); 247 248 SafetyCenterTouchTarget.configureSize( 249 dismissButton, R.dimen.sc_icon_button_touch_target_size); 250 } else { 251 dismissButton.setVisibility(View.GONE); 252 } 253 } 254 255 @Override isSameItem(Preference preference)256 public boolean isSameItem(Preference preference) { 257 return (preference instanceof IssueCardPreference) 258 && TextUtils.equals( 259 mIssue.getId(), ((IssueCardPreference) preference).mIssue.getId()); 260 } 261 262 @Override hasSameContents(Preference preference)263 public boolean hasSameContents(Preference preference) { 264 return (preference instanceof IssueCardPreference) 265 && mIssue.equals(((IssueCardPreference) preference).mIssue) 266 && Objects.equals( 267 mResolvedIssueActionId, 268 ((IssueCardPreference) preference).mResolvedIssueActionId) 269 && mIsDismissed == ((IssueCardPreference) preference).mIsDismissed 270 && mPositionInCardList == ((IssueCardPreference) preference).mPositionInCardList; 271 } 272 273 private class DismissOnClickListener implements View.OnClickListener { 274 @Override onClick(View v)275 public void onClick(View v) { 276 mSafetyCenterViewModel.dismissIssue(mIssue); 277 mSafetyCenterViewModel 278 .getInteractionLogger() 279 .recordForIssue(Action.ISSUE_DISMISS_CLICKED, mIssue, mIsDismissed); 280 } 281 } 282 283 private class ConfirmDismissalOnClickListener implements View.OnClickListener { 284 @Override onClick(View v)285 public void onClick(View v) { 286 ConfirmDismissalDialogFragment.newInstance(mIssue) 287 .showNow(mDialogFragmentManager, /* tag= */ null); 288 } 289 } 290 291 /** Fragment to display a dismissal confirmation dialog for an {@link IssueCardPreference}. */ 292 public static class ConfirmDismissalDialogFragment extends DialogFragment { 293 private static final String ISSUE_KEY = "confirm_dialog_sc_issue"; 294 newInstance(SafetyCenterIssue issue)295 private static ConfirmDismissalDialogFragment newInstance(SafetyCenterIssue issue) { 296 ConfirmDismissalDialogFragment fragment = new ConfirmDismissalDialogFragment(); 297 298 Bundle args = new Bundle(); 299 args.putParcelable(ISSUE_KEY, issue); 300 fragment.setArguments(args); 301 302 return fragment; 303 } 304 305 @Override onCreateDialog(@ullable Bundle savedInstanceState)306 public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 307 SafetyCenterViewModel safetyCenterViewModel = 308 ((SafetyCenterFragment) requireParentFragment()).getSafetyCenterViewModel(); 309 SafetyCenterIssue issue = 310 requireNonNull( 311 requireArguments().getParcelable(ISSUE_KEY, SafetyCenterIssue.class)); 312 return new AlertDialog.Builder(getContext()) 313 .setTitle(R.string.safety_center_issue_card_dismiss_confirmation_title) 314 .setMessage(R.string.safety_center_issue_card_dismiss_confirmation_message) 315 .setPositiveButton( 316 R.string.safety_center_issue_card_confirm_dismiss_button, 317 (dialog, which) -> { 318 safetyCenterViewModel.dismissIssue(issue); 319 safetyCenterViewModel 320 .getInteractionLogger() 321 .recordForIssue( 322 Action.ISSUE_DISMISS_CLICKED, 323 issue, 324 // You can only dismiss non-dismissed issues 325 /* isDismissed= */ false); 326 }) 327 .setNegativeButton( 328 R.string.safety_center_issue_card_cancel_dismiss_button, null) 329 .create(); 330 } 331 } 332 333 /** A dialog to prompt for a confirmation to performn an Action. */ 334 @RequiresApi(UPSIDE_DOWN_CAKE) 335 public static class ConfirmActionDialogFragment extends DialogFragment { 336 private static final String ISSUE_KEY = "issue"; 337 private static final String ACTION_KEY = "action"; 338 private static final String TASK_ID_KEY = "taskId"; 339 private static final String IS_PRIMARY_BUTTON_KEY = "isPrimaryButton"; 340 private static final String IS_DISMISSED_KEY = "isDismissed"; 341 342 /** Create new fragment with the data it will need. */ newInstance( SafetyCenterIssue issue, SafetyCenterIssue.Action action, @Nullable Integer taskId, boolean isFirstButton, boolean isDismissed)343 public static ConfirmActionDialogFragment newInstance( 344 SafetyCenterIssue issue, 345 SafetyCenterIssue.Action action, 346 @Nullable Integer taskId, 347 boolean isFirstButton, 348 boolean isDismissed) { 349 ConfirmActionDialogFragment fragment = new ConfirmActionDialogFragment(); 350 351 Bundle args = new Bundle(); 352 args.putParcelable(ISSUE_KEY, issue); 353 args.putParcelable(ACTION_KEY, action); 354 args.putBoolean(IS_PRIMARY_BUTTON_KEY, isFirstButton); 355 args.putBoolean(IS_DISMISSED_KEY, isDismissed); 356 357 if (taskId != null) { 358 args.putInt(TASK_ID_KEY, taskId); 359 } 360 361 fragment.setArguments(args); 362 363 return fragment; 364 } 365 366 @Override onCreateDialog(Bundle savedInstanceState)367 public Dialog onCreateDialog(Bundle savedInstanceState) { 368 SafetyCenterViewModel safetyCenterViewModel = 369 ((SafetyCenterFragment) requireParentFragment()).getSafetyCenterViewModel(); 370 SafetyCenterIssue issue = 371 requireNonNull( 372 requireArguments().getParcelable(ISSUE_KEY, SafetyCenterIssue.class)); 373 SafetyCenterIssue.Action action = 374 requireNonNull( 375 requireArguments() 376 .getParcelable(ACTION_KEY, SafetyCenterIssue.Action.class)); 377 boolean isPrimaryButton = requireArguments().getBoolean(IS_PRIMARY_BUTTON_KEY); 378 boolean isDismissed = requireArguments().getBoolean(IS_DISMISSED_KEY); 379 380 Integer taskId = 381 requireArguments().containsKey(TASK_ID_KEY) 382 ? requireArguments().getInt(TASK_ID_KEY) 383 : null; 384 385 return new AlertDialog.Builder(getContext()) 386 .setTitle(action.getConfirmationDialogDetails().getTitle()) 387 .setMessage(action.getConfirmationDialogDetails().getText()) 388 .setPositiveButton( 389 action.getConfirmationDialogDetails().getAcceptButtonText(), 390 (dialog, which) -> { 391 safetyCenterViewModel.executeIssueAction(issue, action, taskId); 392 // TODO(b/269097766): Is this the best logging model? 393 safetyCenterViewModel 394 .getInteractionLogger() 395 .recordForIssue( 396 isPrimaryButton 397 ? Action.ISSUE_PRIMARY_ACTION_CLICKED 398 : Action.ISSUE_SECONDARY_ACTION_CLICKED, 399 issue, 400 isDismissed); 401 }) 402 .setNegativeButton( 403 action.getConfirmationDialogDetails().getDenyButtonText(), null) 404 .create(); 405 } 406 } 407 408 private void markIssueResolvedUiCompleted() { 409 if (mResolvedIssueActionId != null) { 410 mResolvedIssueActionId = null; 411 mSafetyCenterViewModel.markIssueResolvedUiCompleted(mIssue.getId()); 412 } 413 } 414 415 private class ActionButtonBuilder { 416 private final SafetyCenterIssue.Action mAction; 417 private final Context mContext; 418 private final ContextThemeWrapper mContextThemeWrapper; 419 private int mIndex; 420 private int mActionButtonListSize; 421 private boolean mIsDismissed = false; 422 private boolean mIsLargeScreen = false; 423 424 ActionButtonBuilder(SafetyCenterIssue.Action action, Context context) { 425 mAction = action; 426 mContext = context; 427 mContextThemeWrapper = 428 new ContextThemeWrapper(context, R.style.Theme_MaterialComponents_DayNight); 429 } 430 431 public ActionButtonBuilder setIndex(int index) { 432 mIndex = index; 433 return this; 434 } 435 436 public ActionButtonBuilder setActionButtonListSize(int actionButtonListSize) { 437 mActionButtonListSize = actionButtonListSize; 438 return this; 439 } 440 441 public ActionButtonBuilder setIsDismissed(boolean isDismissed) { 442 mIsDismissed = isDismissed; 443 return this; 444 } 445 446 public ActionButtonBuilder setIsLargeScreen(boolean isLargeScreen) { 447 mIsLargeScreen = isLargeScreen; 448 return this; 449 } 450 451 private boolean isPrimaryButton() { 452 return mIndex == 0; 453 } 454 455 private boolean isLastButton() { 456 return mIndex == (mActionButtonListSize - 1); 457 } 458 459 private boolean isFilled() { 460 return isPrimaryButton() && !mIsDismissed; 461 } 462 463 public void buildAndAddToView(LinearLayout buttonList) { 464 MaterialButton button = new MaterialButton(mContextThemeWrapper, null, getStyle()); 465 if (SdkLevel.isAtLeastU() && !mIsLargeScreen) { 466 configureGroupStyleCorners(button); 467 } 468 setButtonColors(button); 469 setButtonLayout(button); 470 button.setText(mAction.getLabel()); 471 button.setEnabled(!mAction.isInFlight()); 472 button.setOnClickListener( 473 view -> { 474 if (SdkLevel.isAtLeastU() 475 && mAction.getConfirmationDialogDetails() != null) { 476 ConfirmActionDialogFragment.newInstance( 477 mIssue, 478 mAction, 479 mTaskId, 480 isPrimaryButton(), 481 mIsDismissed) 482 .showNow(mDialogFragmentManager, /* tag= */ null); 483 } else { 484 if (mAction.willResolve()) { 485 // Without a confirmation, the button remains tappable. Disable the 486 // button to prevent double-taps. 487 // We ideally want to do this on any button press, however out of an 488 // abundance of caution we only do it with actions that indicate 489 // they will resolve (and therefore we can rely on a model update to 490 // redraw state - either to isInFlight() or simply resolving the 491 // issue. 492 button.setEnabled(false); 493 } 494 mSafetyCenterViewModel.executeIssueAction(mIssue, mAction, mTaskId); 495 mSafetyCenterViewModel 496 .getInteractionLogger() 497 .recordForIssue( 498 isPrimaryButton() 499 ? Action.ISSUE_PRIMARY_ACTION_CLICKED 500 : Action.ISSUE_SECONDARY_ACTION_CLICKED, 501 mIssue, 502 mIsDismissed); 503 } 504 }); 505 506 maybeAddSpaceToView(buttonList); 507 buttonList.addView(button); 508 } 509 510 /** 511 * Configures "group-style" corners for this button, where the first button in the list has 512 * large corners on top and the last button in the list has large corners on bottom. 513 */ 514 @RequiresApi(UPSIDE_DOWN_CAKE) 515 private void configureGroupStyleCorners(MaterialButton button) { 516 button.setCornerRadiusResource(R.dimen.sc_button_corner_radius_small); 517 ShapeAppearanceModel.Builder shapeAppearanceModelBuilder = 518 button.getShapeAppearanceModel().toBuilder(); 519 520 CornerSize largeCornerSize = 521 new AbsoluteCornerSize( 522 mContext.getResources() 523 .getDimensionPixelSize(R.dimen.sc_button_corner_radius)); 524 if (isPrimaryButton()) { 525 shapeAppearanceModelBuilder 526 .setTopLeftCornerSize(largeCornerSize) 527 .setTopRightCornerSize(largeCornerSize); 528 } 529 if (isLastButton()) { 530 shapeAppearanceModelBuilder 531 .setBottomLeftCornerSize(largeCornerSize) 532 .setBottomRightCornerSize(largeCornerSize); 533 } 534 535 button.setShapeAppearanceModel(shapeAppearanceModelBuilder.build()); 536 } 537 538 private void maybeAddSpaceToView(LinearLayout buttonList) { 539 if (isPrimaryButton()) { 540 return; 541 } 542 543 int margin = 544 mContext.getResources() 545 .getDimensionPixelSize(R.dimen.sc_action_button_list_margin); 546 Space space = new Space(mContext); 547 space.setLayoutParams(new ViewGroup.LayoutParams(margin, margin)); 548 buttonList.addView(space); 549 } 550 551 private int getStyle() { 552 return isFilled() ? R.attr.scActionButtonStyle : R.attr.scSecondaryActionButtonStyle; 553 } 554 555 private void setButtonColors(MaterialButton button) { 556 if (isFilled()) { 557 button.setBackgroundTintList( 558 ContextCompat.getColorStateList( 559 mContext, 560 getPrimaryButtonColorFromSeverity(mIssue.getSeverityLevel()))); 561 } else { 562 button.setStrokeColor( 563 ContextCompat.getColorStateList( 564 mContext, 565 getSecondaryButtonStrokeColorFromSeverity( 566 mIssue.getSeverityLevel()))); 567 } 568 } 569 570 private void setButtonLayout(Button button) { 571 MarginLayoutParams layoutParams = new MarginLayoutParams(layoutWidth(), WRAP_CONTENT); 572 button.setLayoutParams(layoutParams); 573 } 574 575 private int layoutWidth() { 576 if (mIsLargeScreen) { 577 return WRAP_CONTENT; 578 } else { 579 return MATCH_PARENT; 580 } 581 } 582 583 @ColorRes 584 private int getPrimaryButtonColorFromSeverity(int issueSeverityLevel) { 585 return pickColorForSeverityLevel( 586 issueSeverityLevel, 587 R.color.safety_center_button_info, 588 R.color.safety_center_button_recommend, 589 R.color.safety_center_button_warn); 590 } 591 592 @ColorRes 593 private int getSecondaryButtonStrokeColorFromSeverity(int issueSeverityLevel) { 594 return pickColorForSeverityLevel( 595 issueSeverityLevel, 596 R.color.safety_center_outline_button_info, 597 R.color.safety_center_outline_button_recommend, 598 R.color.safety_center_outline_button_warn); 599 } 600 601 @ColorRes 602 private int pickColorForSeverityLevel( 603 int issueSeverityLevel, 604 @ColorRes int infoColor, 605 @ColorRes int recommendColor, 606 @ColorRes int warnColor) { 607 switch (issueSeverityLevel) { 608 case SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK: 609 return infoColor; 610 case SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION: 611 return recommendColor; 612 case SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING: 613 return warnColor; 614 default: 615 Log.w( 616 TAG, 617 String.format("Unexpected issueSeverityLevel: %s", issueSeverityLevel)); 618 return infoColor; 619 } 620 } 621 } 622 } 623