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