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