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.video.impl; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.util.AttributeSet; 24 import android.view.SoundEffectConstants; 25 import android.widget.Checkable; 26 import android.widget.ImageButton; 27 28 /** Image button that maintains a checked state. */ 29 public class CheckableImageButton extends ImageButton implements Checkable { 30 31 private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked}; 32 33 /** Callback interface to notify when the button's checked state has changed */ 34 public interface OnCheckedChangeListener { 35 onCheckedChanged(CheckableImageButton button, boolean isChecked)36 void onCheckedChanged(CheckableImageButton button, boolean isChecked); 37 } 38 39 private boolean broadcasting; 40 private boolean isChecked; 41 private OnCheckedChangeListener onCheckedChangeListener; 42 private CharSequence contentDescriptionChecked; 43 private CharSequence contentDescriptionUnchecked; 44 CheckableImageButton(Context context)45 public CheckableImageButton(Context context) { 46 this(context, null); 47 } 48 CheckableImageButton(Context context, AttributeSet attrs)49 public CheckableImageButton(Context context, AttributeSet attrs) { 50 this(context, attrs, 0); 51 } 52 CheckableImageButton(Context context, AttributeSet attrs, int defStyleAttr)53 public CheckableImageButton(Context context, AttributeSet attrs, int defStyleAttr) { 54 super(context, attrs, defStyleAttr); 55 init(context, attrs); 56 } 57 init(Context context, AttributeSet attrs)58 private void init(Context context, AttributeSet attrs) { 59 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CheckableImageButton); 60 setChecked(typedArray.getBoolean(R.styleable.CheckableImageButton_android_checked, false)); 61 contentDescriptionChecked = 62 typedArray.getText(R.styleable.CheckableImageButton_contentDescriptionChecked); 63 contentDescriptionUnchecked = 64 typedArray.getText(R.styleable.CheckableImageButton_contentDescriptionUnchecked); 65 typedArray.recycle(); 66 67 updateContentDescription(); 68 setClickable(true); 69 setFocusable(true); 70 } 71 72 @Override setChecked(boolean checked)73 public void setChecked(boolean checked) { 74 performSetChecked(checked); 75 } 76 77 /** 78 * Called when the state of the button should be updated, this should not be the result of user 79 * interaction. 80 * 81 * @param checked {@code true} if the button should be in the checked state, {@code false} 82 * otherwise. 83 */ performSetChecked(boolean checked)84 private void performSetChecked(boolean checked) { 85 if (isChecked() == checked) { 86 return; 87 } 88 isChecked = checked; 89 CharSequence contentDescription = updateContentDescription(); 90 announceForAccessibility(contentDescription); 91 refreshDrawableState(); 92 } 93 updateContentDescription()94 private CharSequence updateContentDescription() { 95 CharSequence contentDescription = 96 isChecked ? contentDescriptionChecked : contentDescriptionUnchecked; 97 setContentDescription(contentDescription); 98 return contentDescription; 99 } 100 101 /** 102 * Called when the user interacts with a button. This should not result in the button updating 103 * state, rather the request should be propagated to the associated listener. 104 * 105 * @param checked {@code true} if the button should be in the checked state, {@code false} 106 * otherwise. 107 */ userRequestedSetChecked(boolean checked)108 private void userRequestedSetChecked(boolean checked) { 109 if (isChecked() == checked) { 110 return; 111 } 112 if (broadcasting) { 113 return; 114 } 115 broadcasting = true; 116 if (onCheckedChangeListener != null) { 117 onCheckedChangeListener.onCheckedChanged(this, checked); 118 } 119 broadcasting = false; 120 } 121 122 @Override isChecked()123 public boolean isChecked() { 124 return isChecked; 125 } 126 127 @Override toggle()128 public void toggle() { 129 userRequestedSetChecked(!isChecked()); 130 } 131 132 @Override onCreateDrawableState(int extraSpace)133 public int[] onCreateDrawableState(int extraSpace) { 134 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 135 if (isChecked()) { 136 mergeDrawableStates(drawableState, CHECKED_STATE_SET); 137 } 138 return drawableState; 139 } 140 141 @Override drawableStateChanged()142 protected void drawableStateChanged() { 143 super.drawableStateChanged(); 144 invalidate(); 145 } 146 setOnCheckedChangeListener(OnCheckedChangeListener listener)147 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { 148 this.onCheckedChangeListener = listener; 149 } 150 151 @Override performClick()152 public boolean performClick() { 153 if (!isCheckable()) { 154 return super.performClick(); 155 } 156 157 toggle(); 158 final boolean handled = super.performClick(); 159 if (!handled) { 160 // View only makes a sound effect if the onClickListener was 161 // called, so we'll need to make one here instead. 162 playSoundEffect(SoundEffectConstants.CLICK); 163 } 164 return handled; 165 } 166 isCheckable()167 private boolean isCheckable() { 168 return onCheckedChangeListener != null; 169 } 170 171 @Override onRestoreInstanceState(Parcelable state)172 protected void onRestoreInstanceState(Parcelable state) { 173 SavedState savedState = (SavedState) state; 174 super.onRestoreInstanceState(savedState.getSuperState()); 175 performSetChecked(savedState.isChecked); 176 requestLayout(); 177 } 178 179 @Override onSaveInstanceState()180 protected Parcelable onSaveInstanceState() { 181 return new SavedState(isChecked(), super.onSaveInstanceState()); 182 } 183 184 private static class SavedState extends BaseSavedState { 185 186 public final boolean isChecked; 187 SavedState(boolean isChecked, Parcelable superState)188 private SavedState(boolean isChecked, Parcelable superState) { 189 super(superState); 190 this.isChecked = isChecked; 191 } 192 SavedState(Parcel in)193 protected SavedState(Parcel in) { 194 super(in); 195 isChecked = in.readByte() != 0; 196 } 197 198 public static final Creator<SavedState> CREATOR = 199 new Creator<SavedState>() { 200 @Override 201 public SavedState createFromParcel(Parcel in) { 202 return new SavedState(in); 203 } 204 205 @Override 206 public SavedState[] newArray(int size) { 207 return new SavedState[size]; 208 } 209 }; 210 211 @Override describeContents()212 public int describeContents() { 213 return 0; 214 } 215 216 @Override writeToParcel(Parcel dest, int flags)217 public void writeToParcel(Parcel dest, int flags) { 218 super.writeToParcel(dest, flags); 219 dest.writeByte((byte) (isChecked ? 1 : 0)); 220 } 221 } 222 } 223