• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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