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