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