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.google.android.setupcompat.template; 18 19 import static com.google.android.setupcompat.internal.Preconditions.ensureOnMainThread; 20 21 import android.annotation.SuppressLint; 22 import android.annotation.TargetApi; 23 import android.content.Context; 24 import android.content.res.ColorStateList; 25 import android.content.res.TypedArray; 26 import android.graphics.Color; 27 import android.os.Build; 28 import android.os.Build.VERSION_CODES; 29 import android.os.PersistableBundle; 30 import android.util.AttributeSet; 31 import android.view.ContextThemeWrapper; 32 import android.view.LayoutInflater; 33 import android.view.View; 34 import android.view.ViewStub; 35 import android.widget.Button; 36 import android.widget.LinearLayout; 37 import android.widget.LinearLayout.LayoutParams; 38 import androidx.annotation.AttrRes; 39 import androidx.annotation.CallSuper; 40 import androidx.annotation.ColorInt; 41 import androidx.annotation.IdRes; 42 import androidx.annotation.LayoutRes; 43 import androidx.annotation.MainThread; 44 import androidx.annotation.NonNull; 45 import androidx.annotation.Nullable; 46 import androidx.annotation.StyleRes; 47 import androidx.annotation.VisibleForTesting; 48 import com.google.android.setupcompat.PartnerCustomizationLayout; 49 import com.google.android.setupcompat.R; 50 import com.google.android.setupcompat.internal.FooterButtonPartnerConfig; 51 import com.google.android.setupcompat.internal.TemplateLayout; 52 import com.google.android.setupcompat.logging.internal.FooterBarMixinMetrics; 53 import com.google.android.setupcompat.partnerconfig.PartnerConfig; 54 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; 55 import com.google.android.setupcompat.template.FooterButton.ButtonType; 56 import java.util.Locale; 57 import java.util.concurrent.atomic.AtomicInteger; 58 59 /** 60 * A {@link Mixin} for managing buttons. By default, the button bar expects that buttons on the 61 * start (left for LTR) are "secondary" borderless buttons, while buttons on the end (right for LTR) 62 * are "primary" accent-colored buttons. 63 */ 64 public class FooterBarMixin implements Mixin { 65 66 private final Context context; 67 68 @Nullable private final ViewStub footerStub; 69 70 @VisibleForTesting final boolean applyPartnerResources; 71 @VisibleForTesting final boolean applyDynamicColor; 72 @VisibleForTesting final boolean useFullDynamicColor; 73 74 private LinearLayout buttonContainer; 75 private FooterButton primaryButton; 76 private FooterButton secondaryButton; 77 @IdRes private int primaryButtonId; 78 @IdRes private int secondaryButtonId; 79 ColorStateList primaryDefaultTextColor = null; 80 ColorStateList secondaryDefaultTextColor = null; 81 @VisibleForTesting public FooterButtonPartnerConfig primaryButtonPartnerConfigForTesting; 82 @VisibleForTesting public FooterButtonPartnerConfig secondaryButtonPartnerConfigForTesting; 83 84 private int footerBarPaddingTop; 85 private int footerBarPaddingBottom; 86 @VisibleForTesting int defaultPadding; 87 @ColorInt private final int footerBarPrimaryBackgroundColor; 88 @ColorInt private final int footerBarSecondaryBackgroundColor; 89 private boolean removeFooterBarWhenEmpty = true; 90 private boolean isSecondaryButtonInPrimaryStyle = false; 91 92 private static final AtomicInteger nextGeneratedId = new AtomicInteger(1); 93 94 @VisibleForTesting public final FooterBarMixinMetrics metrics = new FooterBarMixinMetrics(); 95 createButtonEventListener(@dRes int id)96 private FooterButton.OnButtonEventListener createButtonEventListener(@IdRes int id) { 97 98 return new FooterButton.OnButtonEventListener() { 99 100 @Override 101 public void onEnabledChanged(boolean enabled) { 102 if (buttonContainer != null) { 103 Button button = buttonContainer.findViewById(id); 104 if (button != null) { 105 button.setEnabled(enabled); 106 if (applyPartnerResources && !applyDynamicColor) { 107 updateButtonTextColorWithEnabledState( 108 button, 109 (id == primaryButtonId || isSecondaryButtonInPrimaryStyle) 110 ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR 111 : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR); 112 } 113 } 114 } 115 } 116 117 @Override 118 public void onVisibilityChanged(int visibility) { 119 if (buttonContainer != null) { 120 Button button = buttonContainer.findViewById(id); 121 if (button != null) { 122 button.setVisibility(visibility); 123 autoSetButtonBarVisibility(); 124 } 125 } 126 } 127 128 @Override 129 public void onTextChanged(CharSequence text) { 130 if (buttonContainer != null) { 131 Button button = buttonContainer.findViewById(id); 132 if (button != null) { 133 button.setText(text); 134 } 135 } 136 } 137 138 @Override 139 @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) 140 public void onLocaleChanged(Locale locale) { 141 if (buttonContainer != null) { 142 Button button = buttonContainer.findViewById(id); 143 if (button != null && locale != null) { 144 button.setTextLocale(locale); 145 } 146 } 147 } 148 149 @Override 150 @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) 151 public void onDirectionChanged(int direction) { 152 if (buttonContainer != null && direction != -1) { 153 buttonContainer.setLayoutDirection(direction); 154 } 155 } 156 }; 157 } 158 159 /** 160 * Creates a mixin for managing buttons on the footer. 161 * 162 * @param layout The {@link TemplateLayout} containing this mixin. 163 * @param attrs XML attributes given to the layout. 164 * @param defStyleAttr The default style attribute as given to the constructor of the layout. 165 */ 166 public FooterBarMixin( 167 TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { 168 context = layout.getContext(); 169 footerStub = layout.findManagedViewById(R.id.suc_layout_footer); 170 this.applyPartnerResources = 171 layout instanceof PartnerCustomizationLayout 172 && ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource(); 173 174 applyDynamicColor = 175 layout instanceof PartnerCustomizationLayout 176 && ((PartnerCustomizationLayout) layout).shouldApplyDynamicColor(); 177 178 useFullDynamicColor = 179 layout instanceof PartnerCustomizationLayout 180 && ((PartnerCustomizationLayout) layout).useFullDynamicColor(); 181 182 TypedArray a = 183 context.obtainStyledAttributes(attrs, R.styleable.SucFooterBarMixin, defStyleAttr, 0); 184 defaultPadding = 185 a.getDimensionPixelSize(R.styleable.SucFooterBarMixin_sucFooterBarPaddingVertical, 0); 186 footerBarPaddingTop = 187 a.getDimensionPixelSize( 188 R.styleable.SucFooterBarMixin_sucFooterBarPaddingTop, defaultPadding); 189 footerBarPaddingBottom = 190 a.getDimensionPixelSize( 191 R.styleable.SucFooterBarMixin_sucFooterBarPaddingBottom, defaultPadding); 192 footerBarPrimaryBackgroundColor = 193 a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterBackground, 0); 194 footerBarSecondaryBackgroundColor = 195 a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarSecondaryFooterBackground, 0); 196 197 int primaryBtn = 198 a.getResourceId(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterButton, 0); 199 int secondaryBtn = 200 a.getResourceId(R.styleable.SucFooterBarMixin_sucFooterBarSecondaryFooterButton, 0); 201 a.recycle(); 202 203 FooterButtonInflater inflater = new FooterButtonInflater(context); 204 205 if (secondaryBtn != 0) { 206 setSecondaryButton(inflater.inflate(secondaryBtn)); 207 metrics.logPrimaryButtonInitialStateVisibility(/* isVisible= */ true, /* isUsingXml= */ true); 208 } 209 210 if (primaryBtn != 0) { 211 setPrimaryButton(inflater.inflate(primaryBtn)); 212 metrics.logSecondaryButtonInitialStateVisibility( 213 /* isVisible= */ true, /* isUsingXml= */ true); 214 } 215 } 216 217 private View addSpace() { 218 LinearLayout buttonContainer = ensureFooterInflated(); 219 View space = new View(buttonContainer.getContext()); 220 space.setLayoutParams(new LayoutParams(0, 0, 1.0f)); 221 space.setVisibility(View.INVISIBLE); 222 buttonContainer.addView(space); 223 return space; 224 } 225 226 @NonNull 227 private LinearLayout ensureFooterInflated() { 228 if (buttonContainer == null) { 229 if (footerStub == null) { 230 throw new IllegalStateException("Footer stub is not found in this template"); 231 } 232 buttonContainer = (LinearLayout) inflateFooter(R.layout.suc_footer_button_bar); 233 onFooterBarInflated(buttonContainer); 234 onFooterBarApplyPartnerResource(buttonContainer); 235 } 236 return buttonContainer; 237 } 238 239 /** 240 * Notifies that the footer bar has been inflated to the view hierarchy. Calling super is 241 * necessary while subclass implement it. 242 */ 243 @CallSuper 244 protected void onFooterBarInflated(LinearLayout buttonContainer) { 245 if (buttonContainer == null) { 246 // Ignore action since buttonContainer is null 247 return; 248 } 249 if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { 250 buttonContainer.setId(View.generateViewId()); 251 } else { 252 buttonContainer.setId(generateViewId()); 253 } 254 updateFooterBarPadding( 255 buttonContainer, 256 buttonContainer.getPaddingLeft(), 257 footerBarPaddingTop, 258 buttonContainer.getPaddingRight(), 259 footerBarPaddingBottom); 260 } 261 262 /** 263 * Notifies while the footer bar apply Partner Resource. Calling super is necessary while subclass 264 * implement it. 265 */ 266 @CallSuper 267 protected void onFooterBarApplyPartnerResource(LinearLayout buttonContainer) { 268 if (buttonContainer == null) { 269 // Ignore action since buttonContainer is null 270 return; 271 } 272 if (!applyPartnerResources) { 273 return; 274 } 275 276 // skip apply partner resources on footerbar background if dynamic color enabled 277 if (!useFullDynamicColor) { 278 @ColorInt 279 int color = 280 PartnerConfigHelper.get(context) 281 .getColor(context, PartnerConfig.CONFIG_FOOTER_BAR_BG_COLOR); 282 buttonContainer.setBackgroundColor(color); 283 } 284 285 footerBarPaddingTop = 286 (int) 287 PartnerConfigHelper.get(context) 288 .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_TOP); 289 footerBarPaddingBottom = 290 (int) 291 PartnerConfigHelper.get(context) 292 .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_BOTTOM); 293 updateFooterBarPadding( 294 buttonContainer, 295 buttonContainer.getPaddingLeft(), 296 footerBarPaddingTop, 297 buttonContainer.getPaddingRight(), 298 footerBarPaddingBottom); 299 300 if (PartnerConfigHelper.get(context) 301 .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BAR_MIN_HEIGHT)) { 302 int minHeight = 303 (int) 304 PartnerConfigHelper.get(context) 305 .getDimension(context, PartnerConfig.CONFIG_FOOTER_BAR_MIN_HEIGHT); 306 if (minHeight > 0) { 307 buttonContainer.setMinimumHeight(minHeight); 308 } 309 } 310 } 311 312 /** 313 * Inflate FooterActionButton with layout "suc_button". Subclasses can implement this method to 314 * modify the footer button layout as necessary. 315 */ 316 @SuppressLint("InflateParams") 317 protected FooterActionButton createThemedButton(Context context, @StyleRes int theme) { 318 // Inflate a single button from XML, which when using support lib, will take advantage of 319 // the injected layout inflater and give us AppCompatButton instead. 320 LayoutInflater inflater = LayoutInflater.from(new ContextThemeWrapper(context, theme)); 321 return (FooterActionButton) inflater.inflate(R.layout.suc_button, null, false); 322 } 323 324 /** Sets primary button for footer. */ 325 @MainThread 326 public void setPrimaryButton(FooterButton footerButton) { 327 ensureOnMainThread("setPrimaryButton"); 328 ensureFooterInflated(); 329 330 // Setup button partner config 331 FooterButtonPartnerConfig footerButtonPartnerConfig = 332 new FooterButtonPartnerConfig.Builder(footerButton) 333 .setPartnerTheme( 334 getPartnerTheme( 335 footerButton, 336 /* defaultPartnerTheme= */ R.style.SucPartnerCustomizationButton_Primary, 337 /* buttonBackgroundColorConfig= */ PartnerConfig 338 .CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)) 339 .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR) 340 .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) 341 .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) 342 .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) 343 .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) 344 .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) 345 .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR) 346 .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) 347 .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) 348 .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) 349 .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE) 350 .build(); 351 352 FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig); 353 // update information for primary button. Need to update as long as the button inflated. 354 primaryButtonId = button.getId(); 355 primaryDefaultTextColor = button.getTextColors(); 356 primaryButton = footerButton; 357 primaryButtonPartnerConfigForTesting = footerButtonPartnerConfig; 358 359 onFooterButtonInflated(button, footerBarPrimaryBackgroundColor); 360 onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig); 361 362 // Make sure the position of buttons are correctly and prevent primary button create twice or 363 // more. 364 repopulateButtons(); 365 } 366 367 /** Returns the {@link FooterButton} of primary button. */ 368 public FooterButton getPrimaryButton() { 369 return primaryButton; 370 } 371 372 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 373 public Button getPrimaryButtonView() { 374 return buttonContainer == null ? null : buttonContainer.findViewById(primaryButtonId); 375 } 376 377 @VisibleForTesting 378 boolean isPrimaryButtonVisible() { 379 return getPrimaryButtonView() != null && getPrimaryButtonView().getVisibility() == View.VISIBLE; 380 } 381 382 /** Sets secondary button for footer. */ 383 @MainThread 384 public void setSecondaryButton(FooterButton footerButton) { 385 setSecondaryButton(footerButton, /*usePrimaryStyle= */ false); 386 } 387 388 /** Sets secondary button for footer. Allow to use the primary button style. */ 389 @MainThread 390 public void setSecondaryButton(FooterButton footerButton, boolean usePrimaryStyle) { 391 ensureOnMainThread("setSecondaryButton"); 392 isSecondaryButtonInPrimaryStyle = usePrimaryStyle; 393 ensureFooterInflated(); 394 395 // Setup button partner config 396 FooterButtonPartnerConfig footerButtonPartnerConfig = 397 new FooterButtonPartnerConfig.Builder(footerButton) 398 .setPartnerTheme( 399 getPartnerTheme( 400 footerButton, 401 /* defaultPartnerTheme= */ usePrimaryStyle 402 ? R.style.SucPartnerCustomizationButton_Primary 403 : R.style.SucPartnerCustomizationButton_Secondary, 404 /* buttonBackgroundColorConfig= */ usePrimaryStyle 405 ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR 406 : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)) 407 .setButtonBackgroundConfig( 408 usePrimaryStyle 409 ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR 410 : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR) 411 .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) 412 .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) 413 .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) 414 .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) 415 .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) 416 .setTextColorConfig( 417 usePrimaryStyle 418 ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR 419 : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR) 420 .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) 421 .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) 422 .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) 423 .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE) 424 .build(); 425 426 FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig); 427 // update information for secondary button. Need to update as long as the button inflated. 428 secondaryButtonId = button.getId(); 429 secondaryDefaultTextColor = button.getTextColors(); 430 secondaryButton = footerButton; 431 secondaryButtonPartnerConfigForTesting = footerButtonPartnerConfig; 432 433 onFooterButtonInflated(button, footerBarSecondaryBackgroundColor); 434 onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig); 435 436 // Make sure the position of buttons are correctly and prevent secondary button create twice or 437 // more. 438 repopulateButtons(); 439 } 440 441 /** 442 * Corrects the order of footer buttons after the button has been inflated to the view hierarchy. 443 * Subclasses can implement this method to modify the order of footer buttons as necessary. 444 */ 445 protected void repopulateButtons() { 446 LinearLayout buttonContainer = ensureFooterInflated(); 447 Button tempPrimaryButton = getPrimaryButtonView(); 448 Button tempSecondaryButton = getSecondaryButtonView(); 449 buttonContainer.removeAllViews(); 450 451 if (tempSecondaryButton != null) { 452 if (isSecondaryButtonInPrimaryStyle) { 453 // Since the secondary button has the same style (with background) as the primary button, 454 // we need to have the left padding equal to the right padding. 455 updateFooterBarPadding( 456 buttonContainer, 457 buttonContainer.getPaddingRight(), 458 buttonContainer.getPaddingTop(), 459 buttonContainer.getPaddingRight(), 460 buttonContainer.getPaddingBottom()); 461 } 462 buttonContainer.addView(tempSecondaryButton); 463 } 464 addSpace(); 465 if (tempPrimaryButton != null) { 466 buttonContainer.addView(tempPrimaryButton); 467 } 468 } 469 470 /** 471 * Notifies that the footer button has been inInflated and add to the view hierarchy. Calling 472 * super is necessary while subclass implement it. 473 */ 474 @CallSuper 475 protected void onFooterButtonInflated(Button button, @ColorInt int defaultButtonBackgroundColor) { 476 // Try to set default background 477 if (defaultButtonBackgroundColor != 0) { 478 FooterButtonStyleUtils.updateButtonBackground(button, defaultButtonBackgroundColor); 479 } else { 480 // TODO: get button background color from activity theme 481 } 482 buttonContainer.addView(button); 483 autoSetButtonBarVisibility(); 484 } 485 486 private int getPartnerTheme( 487 FooterButton footerButton, 488 int defaultPartnerTheme, 489 PartnerConfig buttonBackgroundColorConfig) { 490 int overrideTheme = footerButton.getTheme(); 491 492 // Set the default theme if theme is not set, or when running in setup flow. 493 if (footerButton.getTheme() == 0 || applyPartnerResources) { 494 overrideTheme = defaultPartnerTheme; 495 } 496 // TODO: Make sure customize attributes in theme can be applied during setup flow. 497 // If sets background color to full transparent, the button changes to colored borderless ink 498 // button style. 499 if (applyPartnerResources) { 500 int color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundColorConfig); 501 if (color == Color.TRANSPARENT) { 502 overrideTheme = R.style.SucPartnerCustomizationButton_Secondary; 503 } else if (color != Color.TRANSPARENT) { 504 // TODO: remove the constrain (color != Color.WHITE), need to check all pages 505 // go well without customization. It should be fine since the default value of secondary bg 506 // color is set as transparent. 507 overrideTheme = R.style.SucPartnerCustomizationButton_Primary; 508 } 509 } 510 return overrideTheme; 511 } 512 513 @VisibleForTesting 514 public LinearLayout getButtonContainer() { 515 return buttonContainer; 516 } 517 518 /** Returns the {@link FooterButton} of secondary button. */ 519 public FooterButton getSecondaryButton() { 520 return secondaryButton; 521 } 522 523 /** 524 * Sets whether the footer bar should be removed when there are no footer buttons in the bar. 525 * 526 * @param value True if footer bar is gone, false otherwise. 527 */ 528 public void setRemoveFooterBarWhenEmpty(boolean value) { 529 removeFooterBarWhenEmpty = value; 530 autoSetButtonBarVisibility(); 531 } 532 533 /** 534 * Checks the visibility state of footer buttons to set the visibility state of this footer bar 535 * automatically. 536 */ 537 private void autoSetButtonBarVisibility() { 538 Button primaryButton = getPrimaryButtonView(); 539 Button secondaryButton = getSecondaryButtonView(); 540 boolean primaryVisible = primaryButton != null && primaryButton.getVisibility() == View.VISIBLE; 541 boolean secondaryVisible = 542 secondaryButton != null && secondaryButton.getVisibility() == View.VISIBLE; 543 544 if (buttonContainer != null) { 545 buttonContainer.setVisibility( 546 primaryVisible || secondaryVisible 547 ? View.VISIBLE 548 : removeFooterBarWhenEmpty ? View.GONE : View.INVISIBLE); 549 } 550 } 551 552 /** Returns the visibility status for this footer bar. */ 553 @VisibleForTesting 554 public int getVisibility() { 555 return buttonContainer.getVisibility(); 556 } 557 558 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 559 public Button getSecondaryButtonView() { 560 return buttonContainer == null ? null : buttonContainer.findViewById(secondaryButtonId); 561 } 562 563 @VisibleForTesting 564 boolean isSecondaryButtonVisible() { 565 return getSecondaryButtonView() != null 566 && getSecondaryButtonView().getVisibility() == View.VISIBLE; 567 } 568 569 private static int generateViewId() { 570 for (; ; ) { 571 final int result = nextGeneratedId.get(); 572 // aapt-generated IDs have the high byte nonzero; clamp to the range under that. 573 int newValue = result + 1; 574 if (newValue > 0x00FFFFFF) { 575 newValue = 1; // Roll over to 1, not 0. 576 } 577 if (nextGeneratedId.compareAndSet(result, newValue)) { 578 return result; 579 } 580 } 581 } 582 583 private FooterActionButton inflateButton( 584 FooterButton footerButton, FooterButtonPartnerConfig footerButtonPartnerConfig) { 585 FooterActionButton button = 586 createThemedButton(context, footerButtonPartnerConfig.getPartnerTheme()); 587 if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { 588 button.setId(View.generateViewId()); 589 } else { 590 button.setId(generateViewId()); 591 } 592 593 // apply initial configuration into button view. 594 button.setText(footerButton.getText()); 595 button.setOnClickListener(footerButton); 596 button.setVisibility(footerButton.getVisibility()); 597 button.setEnabled(footerButton.isEnabled()); 598 button.setFooterButton(footerButton); 599 600 footerButton.setOnButtonEventListener(createButtonEventListener(button.getId())); 601 return button; 602 } 603 604 // TODO: Make sure customize attributes in theme can be applied during setup flow. 605 @TargetApi(VERSION_CODES.Q) 606 private void onFooterButtonApplyPartnerResource( 607 Button button, FooterButtonPartnerConfig footerButtonPartnerConfig) { 608 if (!applyPartnerResources) { 609 return; 610 } 611 FooterButtonStyleUtils.applyButtonPartnerResources( 612 context, 613 button, 614 applyDynamicColor, 615 /* isButtonIconAtEnd= */ (button.getId() == primaryButtonId), 616 footerButtonPartnerConfig); 617 if (!applyDynamicColor) { 618 // adjust text color based on enabled state 619 updateButtonTextColorWithEnabledState( 620 button, footerButtonPartnerConfig.getButtonTextColorConfig()); 621 } 622 } 623 624 private void updateButtonTextColorWithEnabledState( 625 Button button, PartnerConfig buttonTextColorConfig) { 626 if (button.isEnabled()) { 627 FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig( 628 context, button, buttonTextColorConfig); 629 } else { 630 FooterButtonStyleUtils.updateButtonTextDisableColor( 631 button, 632 /* is Primary= */ (primaryButtonId == button.getId() || isSecondaryButtonInPrimaryStyle) 633 ? primaryDefaultTextColor 634 : secondaryDefaultTextColor); 635 } 636 } 637 638 private static PartnerConfig getDrawablePartnerConfig(@ButtonType int buttonType) { 639 PartnerConfig result; 640 switch (buttonType) { 641 case ButtonType.ADD_ANOTHER: 642 result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_ADD_ANOTHER; 643 break; 644 case ButtonType.CANCEL: 645 result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_CANCEL; 646 break; 647 case ButtonType.CLEAR: 648 result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_CLEAR; 649 break; 650 case ButtonType.DONE: 651 result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_DONE; 652 break; 653 case ButtonType.NEXT: 654 result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_NEXT; 655 break; 656 case ButtonType.OPT_IN: 657 result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_OPT_IN; 658 break; 659 case ButtonType.SKIP: 660 result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_SKIP; 661 break; 662 case ButtonType.STOP: 663 result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_STOP; 664 break; 665 case ButtonType.OTHER: 666 default: 667 result = null; 668 break; 669 } 670 return result; 671 } 672 673 protected View inflateFooter(@LayoutRes int footer) { 674 if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 675 LayoutInflater inflater = 676 LayoutInflater.from( 677 new ContextThemeWrapper(context, R.style.SucPartnerCustomizationButtonBar_Stackable)); 678 footerStub.setLayoutInflater(inflater); 679 } 680 footerStub.setLayoutResource(footer); 681 return footerStub.inflate(); 682 } 683 684 private void updateFooterBarPadding( 685 LinearLayout buttonContainer, int left, int top, int right, int bottom) { 686 if (buttonContainer == null) { 687 // Ignore action since buttonContainer is null 688 return; 689 } 690 buttonContainer.setPadding(left, top, right, bottom); 691 } 692 693 /** Returns the paddingTop of footer bar. */ 694 @VisibleForTesting 695 int getPaddingTop() { 696 return (buttonContainer != null) ? buttonContainer.getPaddingTop() : footerStub.getPaddingTop(); 697 } 698 699 /** Returns the paddingBottom of footer bar. */ 700 @VisibleForTesting 701 int getPaddingBottom() { 702 return (buttonContainer != null) 703 ? buttonContainer.getPaddingBottom() 704 : footerStub.getPaddingBottom(); 705 } 706 707 /** Uses for notify mixin the view already attached to window. */ 708 public void onAttachedToWindow() { 709 metrics.logPrimaryButtonInitialStateVisibility( 710 /* isVisible= */ isPrimaryButtonVisible(), /* isUsingXml= */ false); 711 metrics.logSecondaryButtonInitialStateVisibility( 712 /* isVisible= */ isSecondaryButtonVisible(), /* isUsingXml= */ false); 713 } 714 715 /** Uses for notify mixin the view already detached from window. */ 716 public void onDetachedFromWindow() { 717 metrics.updateButtonVisibility(isPrimaryButtonVisible(), isSecondaryButtonVisible()); 718 } 719 720 /** 721 * Assigns logging metrics to bundle for PartnerCustomizationLayout to log metrics to SetupWizard. 722 */ 723 @TargetApi(VERSION_CODES.Q) 724 public PersistableBundle getLoggingMetrics() { 725 return metrics.getMetrics(); 726 } 727 } 728