Home | History | Annotate | Download | only in impl
      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 
     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 
     45   public CheckableImageButton(Context context) {
     46     this(context, null);
     47   }
     48 
     49   public CheckableImageButton(Context context, AttributeSet attrs) {
     50     this(context, attrs, 0);
     51   }
     52 
     53   public CheckableImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
     54     super(context, attrs, defStyleAttr);
     55     init(context, attrs);
     56   }
     57 
     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
     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    */
     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 
     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    */
    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
    123   public boolean isChecked() {
    124     return isChecked;
    125   }
    126 
    127   @Override
    128   public void toggle() {
    129     userRequestedSetChecked(!isChecked());
    130   }
    131 
    132   @Override
    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
    142   protected void drawableStateChanged() {
    143     super.drawableStateChanged();
    144     invalidate();
    145   }
    146 
    147   public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
    148     this.onCheckedChangeListener = listener;
    149   }
    150 
    151   @Override
    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 
    167   private boolean isCheckable() {
    168     return onCheckedChangeListener != null;
    169   }
    170 
    171   @Override
    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
    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 
    188     private SavedState(boolean isChecked, Parcelable superState) {
    189       super(superState);
    190       this.isChecked = isChecked;
    191     }
    192 
    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
    212     public int describeContents() {
    213       return 0;
    214     }
    215 
    216     @Override
    217     public void writeToParcel(Parcel dest, int flags) {
    218       super.writeToParcel(dest, flags);
    219       dest.writeByte((byte) (isChecked ? 1 : 0));
    220     }
    221   }
    222 }
    223