1 /* 2 * Copyright 2018 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 java.lang.annotation.RetentionPolicy.SOURCE; 20 21 import android.annotation.TargetApi; 22 import android.content.Context; 23 import android.content.res.TypedArray; 24 import android.os.Build.VERSION_CODES; 25 import android.os.PersistableBundle; 26 import androidx.annotation.IntDef; 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 import androidx.annotation.StringRes; 30 import androidx.annotation.StyleRes; 31 import android.util.AttributeSet; 32 import android.view.View; 33 import android.view.View.OnClickListener; 34 import com.google.android.setupcompat.R; 35 import java.lang.annotation.Retention; 36 37 /** 38 * Definition of a footer button. Clients can use this class to customize attributes like text, 39 * button type and click listener, and FooterBarMixin will inflate a corresponding Button view. 40 */ 41 public final class FooterButton implements OnClickListener { 42 private static final String KEY_BUTTON_ON_CLICK_COUNT = "_onClickCount"; 43 private static final String KEY_BUTTON_TEXT = "_text"; 44 private static final String KEY_BUTTON_TYPE = "_type"; 45 46 @ButtonType private final int buttonType; 47 private CharSequence text; 48 private boolean enabled = true; 49 private int visibility = View.VISIBLE; 50 private int theme; 51 private OnClickListener onClickListener; 52 private OnClickListener onClickListenerWhenDisabled; 53 private OnButtonEventListener buttonListener; 54 private int clickCount = 0; 55 FooterButton(Context context, AttributeSet attrs)56 public FooterButton(Context context, AttributeSet attrs) { 57 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SucFooterButton); 58 text = a.getString(R.styleable.SucFooterButton_android_text); 59 onClickListener = null; 60 buttonType = 61 getButtonTypeValue( 62 a.getInt(R.styleable.SucFooterButton_sucButtonType, /* defValue= */ ButtonType.OTHER)); 63 theme = a.getResourceId(R.styleable.SucFooterButton_android_theme, /* defValue= */ 0); 64 a.recycle(); 65 } 66 67 /** 68 * Allows client customize text, click listener and theme for footer button before Button has been 69 * created. The {@link FooterBarMixin} will inflate a corresponding Button view. 70 * 71 * @param text The text for button. 72 * @param listener The listener for button. 73 * @param buttonType The type of button. 74 * @param theme The theme for button. 75 */ FooterButton( CharSequence text, @Nullable OnClickListener listener, @ButtonType int buttonType, @StyleRes int theme)76 private FooterButton( 77 CharSequence text, 78 @Nullable OnClickListener listener, 79 @ButtonType int buttonType, 80 @StyleRes int theme) { 81 this.text = text; 82 onClickListener = listener; 83 this.buttonType = buttonType; 84 this.theme = theme; 85 } 86 87 /** Returns the text that this footer button is displaying. */ getText()88 public CharSequence getText() { 89 return text; 90 } 91 92 /** 93 * Registers a callback to be invoked when this view of footer button is clicked. 94 * 95 * @param listener The callback that will run 96 */ setOnClickListener(@ullable OnClickListener listener)97 public void setOnClickListener(@Nullable OnClickListener listener) { 98 onClickListener = listener; 99 } 100 101 /** Returns an {@link OnClickListener} of this footer button. */ getOnClickListenerWhenDisabled()102 public OnClickListener getOnClickListenerWhenDisabled() { 103 return onClickListenerWhenDisabled; 104 } 105 106 /** 107 * Registers a callback to be invoked when footer button disabled and touch event has reacted. 108 * 109 * @param listener The callback that will run 110 */ setOnClickListenerWhenDisabled(@ullable OnClickListener listener)111 public void setOnClickListenerWhenDisabled(@Nullable OnClickListener listener) { 112 onClickListenerWhenDisabled = listener; 113 } 114 115 /** Returns the type of this footer button icon. */ 116 @ButtonType getButtonType()117 public int getButtonType() { 118 return buttonType; 119 } 120 121 /** Returns the theme of this footer button. */ 122 @StyleRes getTheme()123 public int getTheme() { 124 return theme; 125 } 126 127 /** 128 * Sets the enabled state of this footer button. 129 * 130 * @param enabled True if this view is enabled, false otherwise. 131 */ setEnabled(boolean enabled)132 public void setEnabled(boolean enabled) { 133 this.enabled = enabled; 134 if (buttonListener != null) { 135 buttonListener.onEnabledChanged(enabled); 136 } 137 } 138 139 /** Returns the enabled status for this footer button. */ isEnabled()140 public boolean isEnabled() { 141 return enabled; 142 } 143 144 /** 145 * Sets the visibility state of this footer button. 146 * 147 * @param visibility one of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. 148 */ setVisibility(int visibility)149 public void setVisibility(int visibility) { 150 this.visibility = visibility; 151 if (buttonListener != null) { 152 buttonListener.onVisibilityChanged(visibility); 153 } 154 } 155 156 /** Returns the visibility status for this footer button. */ getVisibility()157 public int getVisibility() { 158 return visibility; 159 } 160 161 /** Sets the text to be displayed using a string resource identifier. */ setText(Context context, @StringRes int resId)162 public void setText(Context context, @StringRes int resId) { 163 setText(context.getText(resId)); 164 } 165 166 /** Sets the text to be displayed on footer button. */ setText(CharSequence text)167 public void setText(CharSequence text) { 168 this.text = text; 169 if (buttonListener != null) { 170 buttonListener.onTextChanged(text); 171 } 172 } 173 174 /** 175 * Registers a callback to be invoked when footer button API has set. 176 * 177 * @param listener The callback that will run 178 */ setOnButtonEventListener(@ullable OnButtonEventListener listener)179 void setOnButtonEventListener(@Nullable OnButtonEventListener listener) { 180 if (listener != null) { 181 buttonListener = listener; 182 } else { 183 throw new NullPointerException("Event listener of footer button may not be null."); 184 } 185 } 186 187 @Override onClick(View v)188 public void onClick(View v) { 189 if (onClickListener != null) { 190 clickCount++; 191 onClickListener.onClick(v); 192 } 193 } 194 195 /** Interface definition for a callback to be invoked when footer button API has set. */ 196 interface OnButtonEventListener { 197 onEnabledChanged(boolean enabled)198 void onEnabledChanged(boolean enabled); 199 onVisibilityChanged(int visibility)200 void onVisibilityChanged(int visibility); 201 onTextChanged(CharSequence text)202 void onTextChanged(CharSequence text); 203 } 204 205 /** Maximum valid value of ButtonType */ 206 private static final int MAX_BUTTON_TYPE = 8; 207 208 @Retention(SOURCE) 209 @IntDef({ 210 ButtonType.OTHER, 211 ButtonType.ADD_ANOTHER, 212 ButtonType.CANCEL, 213 ButtonType.CLEAR, 214 ButtonType.DONE, 215 ButtonType.NEXT, 216 ButtonType.OPT_IN, 217 ButtonType.SKIP, 218 ButtonType.STOP 219 }) 220 /** 221 * Types for footer button. The button appearance and behavior may change based on its type. In 222 * order to be backward compatible with application built with old version of setupcompat; each 223 * ButtonType should not be changed. 224 */ 225 public @interface ButtonType { 226 /** A type of button that doesn't fit into any other categories. */ 227 int OTHER = 0; 228 /** 229 * A type of button that will set up additional elements of the ongoing setup step(s) when 230 * clicked. 231 */ 232 int ADD_ANOTHER = 1; 233 /** A type of button that will cancel the ongoing setup step(s) and exit setup when clicked. */ 234 int CANCEL = 2; 235 /** A type of button that will clear the progress when clicked. (eg: clear PIN code) */ 236 int CLEAR = 3; 237 /** A type of button that will exit the setup flow when clicked. */ 238 int DONE = 4; 239 /** A type of button that will go to the next screen, or next step in the flow when clicked. */ 240 int NEXT = 5; 241 /** A type of button to opt-in or agree to the features described in the current screen. */ 242 int OPT_IN = 6; 243 /** A type of button that will skip the current step when clicked. */ 244 int SKIP = 7; 245 /** A type of button that will stop the ongoing setup step(s) and skip forward when clicked. */ 246 int STOP = 8; 247 } 248 getButtonTypeValue(int value)249 private int getButtonTypeValue(int value) { 250 if (value >= 0 && value <= MAX_BUTTON_TYPE) { 251 return value; 252 } else { 253 throw new IllegalArgumentException("Not a ButtonType"); 254 } 255 } 256 getButtonTypeName()257 private String getButtonTypeName() { 258 switch (buttonType) { 259 case ButtonType.ADD_ANOTHER: 260 return "ADD_ANOTHER"; 261 case ButtonType.CANCEL: 262 return "CANCEL"; 263 case ButtonType.CLEAR: 264 return "CLEAR"; 265 case ButtonType.DONE: 266 return "DONE"; 267 case ButtonType.NEXT: 268 return "NEXT"; 269 case ButtonType.OPT_IN: 270 return "OPT_IN"; 271 case ButtonType.SKIP: 272 return "SKIP"; 273 case ButtonType.STOP: 274 return "STOP"; 275 case ButtonType.OTHER: 276 default: 277 return "OTHER"; 278 } 279 } 280 281 /** 282 * Returns footer button related metrics bundle for PartnerCustomizationLayout to log to 283 * SetupWizard. 284 */ 285 @TargetApi(VERSION_CODES.Q) getMetrics(String buttonName)286 public PersistableBundle getMetrics(String buttonName) { 287 PersistableBundle bundle = new PersistableBundle(); 288 bundle.putString(buttonName + KEY_BUTTON_TEXT, getText().toString()); 289 bundle.putString(buttonName + KEY_BUTTON_TYPE, getButtonTypeName()); 290 bundle.putInt(buttonName + KEY_BUTTON_ON_CLICK_COUNT, clickCount); 291 return bundle; 292 } 293 294 /** 295 * Builder class for constructing {@code FooterButton} objects. 296 * 297 * <p>Allows client customize text, click listener and theme for footer button before Button has 298 * been created. The {@link FooterBarMixin} will inflate a corresponding Button view. 299 * 300 * <p>Example: 301 * 302 * <pre class="prettyprint"> 303 * FooterButton primaryButton = 304 * new FooterButton.Builder(mContext) 305 * .setText(R.string.primary_button_label) 306 * .setListener(primaryButton) 307 * .setButtonType(ButtonType.NEXT) 308 * .setTheme(R.style.SuwGlifButton_Primary) 309 * .build(); 310 * </pre> 311 */ 312 public static class Builder { 313 private final Context context; 314 private String text = ""; 315 private OnClickListener onClickListener = null; 316 @ButtonType private int buttonType = ButtonType.OTHER; 317 private int theme = 0; 318 Builder(@onNull Context context)319 public Builder(@NonNull Context context) { 320 this.context = context; 321 } 322 323 /** Sets the {@code text} of FooterButton. */ setText(String text)324 public Builder setText(String text) { 325 this.text = text; 326 return this; 327 } 328 329 /** Sets the {@code text} of FooterButton by resource. */ setText(@tringRes int text)330 public Builder setText(@StringRes int text) { 331 this.text = context.getString(text); 332 return this; 333 } 334 335 /** Sets the {@code listener} of FooterButton. */ setListener(@ullable OnClickListener listener)336 public Builder setListener(@Nullable OnClickListener listener) { 337 onClickListener = listener; 338 return this; 339 } 340 341 /** Sets the {@code buttonType} of FooterButton. */ setButtonType(@uttonType int buttonType)342 public Builder setButtonType(@ButtonType int buttonType) { 343 this.buttonType = buttonType; 344 return this; 345 } 346 347 /** Sets the {@code theme} for applying FooterButton. */ setTheme(@tyleRes int theme)348 public Builder setTheme(@StyleRes int theme) { 349 this.theme = theme; 350 return this; 351 } 352 build()353 public FooterButton build() { 354 return new FooterButton(text, onClickListener, buttonType, theme); 355 } 356 } 357 } 358