blob: bfc2781a9fdf78337569df108716c9588c3b87e1 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.incallui.incall.impl;
import android.animation.AnimatorInflater;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.StringRes;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.SoundEffectConstants;
import android.widget.Checkable;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
/** A button to show on the incall screen */
public class CheckableLabeledButton extends LinearLayout implements Checkable {
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
private static final float DISABLED_STATE_OPACITY = .3f;
private boolean broadcasting;
private boolean isChecked;
private OnCheckedChangeListener onCheckedChangeListener;
private ImageView iconView;
@DrawableRes private int iconResource = 0;
private TextView labelView;
private Drawable background;
private Drawable backgroundMore;
public CheckableLabeledButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public CheckableLabeledButton(Context context) {
this(context, null);
}
private void init(Context context, AttributeSet attrs) {
setOrientation(VERTICAL);
setGravity(Gravity.CENTER_HORIZONTAL);
Drawable icon;
CharSequence labelText;
boolean enabled;
backgroundMore =
getResources().getDrawable(R.drawable.incall_button_background_more, context.getTheme());
background =
getResources().getDrawable(R.drawable.incall_button_background, context.getTheme());
TypedArray typedArray =
context.obtainStyledAttributes(attrs, R.styleable.CheckableLabeledButton);
icon = typedArray.getDrawable(R.styleable.CheckableLabeledButton_incall_icon);
labelText = typedArray.getString(R.styleable.CheckableLabeledButton_incall_labelText);
enabled = typedArray.getBoolean(R.styleable.CheckableLabeledButton_android_enabled, true);
typedArray.recycle();
int paddingSize = getResources().getDimensionPixelOffset(R.dimen.incall_button_padding);
setPadding(paddingSize, paddingSize, paddingSize, paddingSize);
int iconSize = getResources().getDimensionPixelSize(R.dimen.incall_labeled_button_size);
int imageSize = getResources().getDimensionPixelSize(R.dimen.incall_labeled_button_icon_size);
int iconPadding = (iconSize - imageSize) / 2;
iconView = new ImageView(context, null, android.R.style.Widget_Material_Button_Colored);
LayoutParams iconParams = generateDefaultLayoutParams();
iconParams.width = iconSize;
iconParams.height = iconSize;
iconView.setLayoutParams(iconParams);
iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding);
iconView.setImageDrawable(icon);
iconView.setImageTintMode(Mode.SRC_IN);
iconView.setImageTintList(
getResources().getColorStateList(R.color.incall_button_icon, context.getTheme()));
iconView.setBackground(
getResources().getDrawable(R.drawable.incall_button_background, context.getTheme()));
iconView.setDuplicateParentStateEnabled(true);
iconView.setElevation(getResources().getDimension(R.dimen.incall_button_elevation));
iconView.setStateListAnimator(
AnimatorInflater.loadStateListAnimator(context, R.animator.incall_button_elevation));
addView(iconView);
labelView = new TextView(context);
LayoutParams labelParams = generateDefaultLayoutParams();
labelParams.width = LayoutParams.WRAP_CONTENT;
labelParams.height = LayoutParams.WRAP_CONTENT;
labelParams.topMargin =
context.getResources().getDimensionPixelOffset(R.dimen.incall_button_label_margin);
labelView.setLayoutParams(labelParams);
labelView.setTextAppearance(R.style.Dialer_Incall_TextAppearance_Label);
labelView.setText(labelText);
labelView.setSingleLine();
labelView.setMaxEms(9);
labelView.setEllipsize(TruncateAt.END);
labelView.setGravity(Gravity.CENTER);
labelView.setDuplicateParentStateEnabled(true);
addView(labelView);
setFocusable(true);
setClickable(true);
setEnabled(enabled);
setOutlineProvider(null);
}
@Override
public void refreshDrawableState() {
super.refreshDrawableState();
iconView.setAlpha(isEnabled() ? 1f : DISABLED_STATE_OPACITY);
labelView.setAlpha(isEnabled() ? 1f : DISABLED_STATE_OPACITY);
}
public void setCheckedColor(@ColorInt int color) {
iconView.setImageTintList(
new ColorStateList(
new int[][] {new int[] {android.R.attr.state_checked}, new int[] {}},
new int[] {color, Color.WHITE}));
}
public Drawable getIconDrawable() {
return iconView.getDrawable();
}
public void setIconDrawable(@DrawableRes int drawableRes) {
if (iconResource != drawableRes) {
iconView.setImageResource(drawableRes);
iconResource = drawableRes;
}
}
public void setLabelText(@StringRes int stringRes) {
labelView.setText(stringRes);
}
/** Shows or hides a little down arrow to indicate that the button will pop up a menu. */
public void setShouldShowMoreIndicator(boolean shouldShow) {
iconView.setBackground(shouldShow ? backgroundMore : background);
}
@Override
public boolean isChecked() {
return isChecked;
}
@Override
public void setChecked(boolean checked) {
performSetChecked(checked);
}
@Override
public void toggle() {
userRequestedSetChecked(!isChecked());
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
invalidate();
}
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
this.onCheckedChangeListener = listener;
}
@Override
public boolean performClick() {
if (!isCheckable()) {
return super.performClick();
}
toggle();
final boolean handled = super.performClick();
if (!handled) {
// View only makes a sound effect if the onClickListener was
// called, so we'll need to make one here instead.
playSoundEffect(SoundEffectConstants.CLICK);
}
return handled;
}
private boolean isCheckable() {
return onCheckedChangeListener != null;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
performSetChecked(savedState.isChecked);
requestLayout();
}
@Override
protected Parcelable onSaveInstanceState() {
return new SavedState(isChecked(), super.onSaveInstanceState());
}
/**
* Called when the state of the button should be updated, this should not be the result of user
* interaction.
*
* @param checked {@code true} if the button should be in the checked state, {@code false}
* otherwise.
*/
private void performSetChecked(boolean checked) {
if (isChecked() == checked) {
return;
}
isChecked = checked;
refreshDrawableState();
}
/**
* Called when the user interacts with a button. This should not result in the button updating
* state, rather the request should be propagated to the associated listener.
*
* @param checked {@code true} if the button should be in the checked state, {@code false}
* otherwise.
*/
private void userRequestedSetChecked(boolean checked) {
if (isChecked() == checked) {
return;
}
if (broadcasting) {
return;
}
broadcasting = true;
if (onCheckedChangeListener != null) {
onCheckedChangeListener.onCheckedChanged(this, checked);
}
broadcasting = false;
}
/** Callback interface to notify when the button's checked state has changed */
public interface OnCheckedChangeListener {
void onCheckedChanged(CheckableLabeledButton checkableLabeledButton, boolean isChecked);
}
private static class SavedState extends BaseSavedState {
public static final Creator<SavedState> CREATOR =
new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
public final boolean isChecked;
private SavedState(boolean isChecked, Parcelable superState) {
super(superState);
this.isChecked = isChecked;
}
protected SavedState(Parcel in) {
super(in);
isChecked = in.readByte() != 0;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeByte((byte) (isChecked ? 1 : 0));
}
}
}