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