| /* |
| * 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.TypedArray; |
| import android.graphics.PorterDuff.Mode; |
| import android.graphics.drawable.Drawable; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| 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; |
| import com.android.dialer.theme.base.ThemeComponent; |
| |
| /** 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 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(ThemeComponent.get(context).theme().getColorIconStateList()); |
| |
| 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); |
| } |
| |
| 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)); |
| } |
| } |
| } |