blob: c0c8599a4a09ec73f12fee822509d71babbb681a [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.rtt.impl;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.SoundEffectConstants;
import android.widget.Button;
import android.widget.Checkable;
/** Image button that maintains a checked state. */
public class RttCheckableButton extends Button implements Checkable {
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
/** Callback interface to notify when the button's checked state has changed */
public interface OnCheckedChangeListener {
void onCheckedChanged(RttCheckableButton button, boolean isChecked);
}
private boolean broadcasting;
private boolean isChecked;
private OnCheckedChangeListener onCheckedChangeListener;
private CharSequence contentDescriptionChecked;
private CharSequence contentDescriptionUnchecked;
public RttCheckableButton(Context context) {
this(context, null);
}
public RttCheckableButton(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.imageButtonStyle);
}
public RttCheckableButton(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public RttCheckableButton(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RttCheckableButton);
setChecked(typedArray.getBoolean(R.styleable.RttCheckableButton_android_checked, false));
contentDescriptionChecked =
typedArray.getText(R.styleable.RttCheckableButton_contentDescriptionChecked);
contentDescriptionUnchecked =
typedArray.getText(R.styleable.RttCheckableButton_contentDescriptionUnchecked);
typedArray.recycle();
updateContentDescription();
setClickable(true);
setFocusable(true);
}
@Override
public void setChecked(boolean checked) {
performSetChecked(checked);
}
/**
* 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;
CharSequence contentDescription = updateContentDescription();
announceForAccessibility(contentDescription);
refreshDrawableState();
}
private CharSequence updateContentDescription() {
CharSequence contentDescription =
isChecked ? contentDescriptionChecked : contentDescriptionUnchecked;
setContentDescription(contentDescription);
return contentDescription;
}
/**
* 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;
}
@Override
public boolean isChecked() {
return isChecked;
}
@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
public void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
performSetChecked(savedState.isChecked);
requestLayout();
}
@Override
public Parcelable onSaveInstanceState() {
return new SavedState(isChecked(), super.onSaveInstanceState());
}
private static class SavedState extends BaseSavedState {
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;
}
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];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeByte((byte) (isChecked ? 1 : 0));
}
}
}