• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.incallui.incall.impl;
18 
19 import android.animation.AnimatorInflater;
20 import android.content.Context;
21 import android.content.res.ColorStateList;
22 import android.content.res.TypedArray;
23 import android.graphics.Color;
24 import android.graphics.PorterDuff.Mode;
25 import android.graphics.drawable.Drawable;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.support.annotation.ColorInt;
29 import android.support.annotation.DrawableRes;
30 import android.support.annotation.StringRes;
31 import android.text.TextUtils.TruncateAt;
32 import android.util.AttributeSet;
33 import android.view.Gravity;
34 import android.view.SoundEffectConstants;
35 import android.widget.Checkable;
36 import android.widget.ImageView;
37 import android.widget.LinearLayout;
38 import android.widget.TextView;
39 
40 /** A button to show on the incall screen */
41 public class CheckableLabeledButton extends LinearLayout implements Checkable {
42 
43   private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
44   private static final float DISABLED_STATE_OPACITY = .3f;
45   private boolean broadcasting;
46   private boolean isChecked;
47   private OnCheckedChangeListener onCheckedChangeListener;
48   private ImageView iconView;
49   @DrawableRes private int iconResource = 0;
50   private TextView labelView;
51   private Drawable background;
52   private Drawable backgroundMore;
53 
CheckableLabeledButton(Context context, AttributeSet attrs)54   public CheckableLabeledButton(Context context, AttributeSet attrs) {
55     super(context, attrs);
56     init(context, attrs);
57   }
58 
CheckableLabeledButton(Context context)59   public CheckableLabeledButton(Context context) {
60     this(context, null);
61   }
62 
init(Context context, AttributeSet attrs)63   private void init(Context context, AttributeSet attrs) {
64     setOrientation(VERTICAL);
65     setGravity(Gravity.CENTER_HORIZONTAL);
66     Drawable icon;
67     CharSequence labelText;
68     boolean enabled;
69 
70     backgroundMore =
71         getResources().getDrawable(R.drawable.incall_button_background_more, context.getTheme());
72     background =
73         getResources().getDrawable(R.drawable.incall_button_background, context.getTheme());
74 
75     TypedArray typedArray =
76         context.obtainStyledAttributes(attrs, R.styleable.CheckableLabeledButton);
77     icon = typedArray.getDrawable(R.styleable.CheckableLabeledButton_incall_icon);
78     labelText = typedArray.getString(R.styleable.CheckableLabeledButton_incall_labelText);
79     enabled = typedArray.getBoolean(R.styleable.CheckableLabeledButton_android_enabled, true);
80     typedArray.recycle();
81 
82     int paddingSize = getResources().getDimensionPixelOffset(R.dimen.incall_button_padding);
83     setPadding(paddingSize, paddingSize, paddingSize, paddingSize);
84 
85     int iconSize = getResources().getDimensionPixelSize(R.dimen.incall_labeled_button_size);
86     int imageSize = getResources().getDimensionPixelSize(R.dimen.incall_labeled_button_icon_size);
87     int iconPadding = (iconSize - imageSize) / 2;
88 
89     iconView = new ImageView(context, null, android.R.style.Widget_Material_Button_Colored);
90     LayoutParams iconParams = generateDefaultLayoutParams();
91     iconParams.width = iconSize;
92     iconParams.height = iconSize;
93     iconView.setLayoutParams(iconParams);
94     iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding);
95     iconView.setImageDrawable(icon);
96     iconView.setImageTintMode(Mode.SRC_IN);
97     iconView.setImageTintList(
98         getResources().getColorStateList(R.color.incall_button_icon, context.getTheme()));
99 
100     iconView.setBackground(
101         getResources().getDrawable(R.drawable.incall_button_background, context.getTheme()));
102     iconView.setDuplicateParentStateEnabled(true);
103     iconView.setElevation(getResources().getDimension(R.dimen.incall_button_elevation));
104     iconView.setStateListAnimator(
105         AnimatorInflater.loadStateListAnimator(context, R.animator.incall_button_elevation));
106     addView(iconView);
107 
108     labelView = new TextView(context);
109     LayoutParams labelParams = generateDefaultLayoutParams();
110     labelParams.width = LayoutParams.WRAP_CONTENT;
111     labelParams.height = LayoutParams.WRAP_CONTENT;
112     labelParams.topMargin =
113         context.getResources().getDimensionPixelOffset(R.dimen.incall_button_label_margin);
114     labelView.setLayoutParams(labelParams);
115     labelView.setTextAppearance(R.style.Dialer_Incall_TextAppearance_Label);
116     labelView.setText(labelText);
117     labelView.setSingleLine();
118     labelView.setMaxEms(9);
119     labelView.setEllipsize(TruncateAt.END);
120     labelView.setGravity(Gravity.CENTER);
121     labelView.setDuplicateParentStateEnabled(true);
122     addView(labelView);
123 
124     setFocusable(true);
125     setClickable(true);
126     setEnabled(enabled);
127     setOutlineProvider(null);
128   }
129 
130   @Override
refreshDrawableState()131   public void refreshDrawableState() {
132     super.refreshDrawableState();
133     iconView.setAlpha(isEnabled() ? 1f : DISABLED_STATE_OPACITY);
134     labelView.setAlpha(isEnabled() ? 1f : DISABLED_STATE_OPACITY);
135   }
136 
setCheckedColor(@olorInt int color)137   public void setCheckedColor(@ColorInt int color) {
138     iconView.setImageTintList(
139         new ColorStateList(
140             new int[][] {new int[] {android.R.attr.state_checked}, new int[] {}},
141             new int[] {color, Color.WHITE}));
142   }
143 
getIconDrawable()144   public Drawable getIconDrawable() {
145     return iconView.getDrawable();
146   }
147 
setIconDrawable(@rawableRes int drawableRes)148   public void setIconDrawable(@DrawableRes int drawableRes) {
149     if (iconResource != drawableRes) {
150       iconView.setImageResource(drawableRes);
151       iconResource = drawableRes;
152     }
153   }
154 
setLabelText(@tringRes int stringRes)155   public void setLabelText(@StringRes int stringRes) {
156     labelView.setText(stringRes);
157   }
158 
159   /** Shows or hides a little down arrow to indicate that the button will pop up a menu. */
setShouldShowMoreIndicator(boolean shouldShow)160   public void setShouldShowMoreIndicator(boolean shouldShow) {
161     iconView.setBackground(shouldShow ? backgroundMore : background);
162   }
163 
164   @Override
isChecked()165   public boolean isChecked() {
166     return isChecked;
167   }
168 
169   @Override
setChecked(boolean checked)170   public void setChecked(boolean checked) {
171     performSetChecked(checked);
172   }
173 
174   @Override
toggle()175   public void toggle() {
176     userRequestedSetChecked(!isChecked());
177   }
178 
179   @Override
onCreateDrawableState(int extraSpace)180   public int[] onCreateDrawableState(int extraSpace) {
181     final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
182     if (isChecked()) {
183       mergeDrawableStates(drawableState, CHECKED_STATE_SET);
184     }
185     return drawableState;
186   }
187 
188   @Override
drawableStateChanged()189   protected void drawableStateChanged() {
190     super.drawableStateChanged();
191     invalidate();
192   }
193 
setOnCheckedChangeListener(OnCheckedChangeListener listener)194   public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
195     this.onCheckedChangeListener = listener;
196   }
197 
198   @Override
performClick()199   public boolean performClick() {
200     if (!isCheckable()) {
201       return super.performClick();
202     }
203 
204     toggle();
205     final boolean handled = super.performClick();
206     if (!handled) {
207       // View only makes a sound effect if the onClickListener was
208       // called, so we'll need to make one here instead.
209       playSoundEffect(SoundEffectConstants.CLICK);
210     }
211     return handled;
212   }
213 
isCheckable()214   private boolean isCheckable() {
215     return onCheckedChangeListener != null;
216   }
217 
218   @Override
onRestoreInstanceState(Parcelable state)219   protected void onRestoreInstanceState(Parcelable state) {
220     SavedState savedState = (SavedState) state;
221     super.onRestoreInstanceState(savedState.getSuperState());
222     performSetChecked(savedState.isChecked);
223     requestLayout();
224   }
225 
226   @Override
onSaveInstanceState()227   protected Parcelable onSaveInstanceState() {
228     return new SavedState(isChecked(), super.onSaveInstanceState());
229   }
230 
231   /**
232    * Called when the state of the button should be updated, this should not be the result of user
233    * interaction.
234    *
235    * @param checked {@code true} if the button should be in the checked state, {@code false}
236    *     otherwise.
237    */
performSetChecked(boolean checked)238   private void performSetChecked(boolean checked) {
239     if (isChecked() == checked) {
240       return;
241     }
242     isChecked = checked;
243     refreshDrawableState();
244   }
245 
246   /**
247    * Called when the user interacts with a button. This should not result in the button updating
248    * state, rather the request should be propagated to the associated listener.
249    *
250    * @param checked {@code true} if the button should be in the checked state, {@code false}
251    *     otherwise.
252    */
userRequestedSetChecked(boolean checked)253   private void userRequestedSetChecked(boolean checked) {
254     if (isChecked() == checked) {
255       return;
256     }
257     if (broadcasting) {
258       return;
259     }
260     broadcasting = true;
261     if (onCheckedChangeListener != null) {
262       onCheckedChangeListener.onCheckedChanged(this, checked);
263     }
264     broadcasting = false;
265   }
266 
267   /** Callback interface to notify when the button's checked state has changed */
268   public interface OnCheckedChangeListener {
269 
onCheckedChanged(CheckableLabeledButton checkableLabeledButton, boolean isChecked)270     void onCheckedChanged(CheckableLabeledButton checkableLabeledButton, boolean isChecked);
271   }
272 
273   private static class SavedState extends BaseSavedState {
274 
275     public static final Creator<SavedState> CREATOR =
276         new Creator<SavedState>() {
277           @Override
278           public SavedState createFromParcel(Parcel in) {
279             return new SavedState(in);
280           }
281 
282           @Override
283           public SavedState[] newArray(int size) {
284             return new SavedState[size];
285           }
286         };
287     public final boolean isChecked;
288 
SavedState(boolean isChecked, Parcelable superState)289     private SavedState(boolean isChecked, Parcelable superState) {
290       super(superState);
291       this.isChecked = isChecked;
292     }
293 
SavedState(Parcel in)294     protected SavedState(Parcel in) {
295       super(in);
296       isChecked = in.readByte() != 0;
297     }
298 
299     @Override
describeContents()300     public int describeContents() {
301       return 0;
302     }
303 
304     @Override
writeToParcel(Parcel dest, int flags)305     public void writeToParcel(Parcel dest, int flags) {
306       super.writeToParcel(dest, flags);
307       dest.writeByte((byte) (isChecked ? 1 : 0));
308     }
309   }
310 }
311