Home | History | Annotate | Download | only in deskclock
      1 /*
      2  * Copyright (C) 2015 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.deskclock;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.Context;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Color;
     23 import android.graphics.drawable.Drawable;
     24 import android.os.Build;
     25 import android.os.Parcel;
     26 import android.os.Parcelable;
     27 import android.util.AttributeSet;
     28 import android.widget.NumberPicker;
     29 
     30 import java.lang.reflect.Field;
     31 
     32 /**
     33  * Subclass of NumberPicker that allows customizing divider color and saves/restores its value
     34  * across device rotations.
     35  */
     36 public class NumberPickerCompat extends NumberPicker implements NumberPicker.OnValueChangeListener {
     37 
     38     private static Field sSelectionDivider;
     39     private static boolean sTrySelectionDivider = true;
     40 
     41     private final Runnable mAnnounceValueRunnable = new Runnable() {
     42         @Override
     43         public void run() {
     44             if (mOnAnnounceValueChangedListener != null) {
     45                 final int value = getValue();
     46                 final String[] displayedValues = getDisplayedValues();
     47                 final String displayedValue =
     48                         displayedValues == null ? null : displayedValues[value];
     49                 mOnAnnounceValueChangedListener.onAnnounceValueChanged(
     50                         NumberPickerCompat.this, value, displayedValue);
     51             }
     52         }
     53     };
     54     private OnValueChangeListener mOnValueChangedListener;
     55     private OnAnnounceValueChangedListener mOnAnnounceValueChangedListener;
     56 
     57     public NumberPickerCompat(Context context) {
     58         this(context, null /* attrs */);
     59     }
     60 
     61     public NumberPickerCompat(Context context, AttributeSet attrs) {
     62         super(context, attrs);
     63         tintSelectionDivider(context);
     64         super.setOnValueChangedListener(this);
     65     }
     66 
     67     public NumberPickerCompat(Context context, AttributeSet attrs, int defStyleAttr) {
     68         super(context, attrs, defStyleAttr);
     69         tintSelectionDivider(context);
     70         super.setOnValueChangedListener(this);
     71     }
     72 
     73     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     74     private void tintSelectionDivider(Context context) {
     75         // Accent color in KK will stay system blue, so leave divider color matching.
     76         // The divider is correctly tinted to controlColorNormal in M.
     77 
     78         if (Utils.isLOrLMR1() && sTrySelectionDivider) {
     79             final TypedArray a = context.obtainStyledAttributes(
     80                     new int[] { android.R.attr.colorControlNormal });
     81              // White is default color if colorControlNormal is not defined.
     82             final int color = a.getColor(0, Color.WHITE);
     83             a.recycle();
     84 
     85             try {
     86                 if (sSelectionDivider == null) {
     87                     sSelectionDivider = NumberPicker.class.getDeclaredField("mSelectionDivider");
     88                     sSelectionDivider.setAccessible(true);
     89                 }
     90                 final Drawable selectionDivider = (Drawable) sSelectionDivider.get(this);
     91                 if (selectionDivider != null) {
     92                     // setTint is API21+, but this will only be called in API21
     93                     selectionDivider.setTint(color);
     94                 }
     95             } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
     96                 LogUtils.e("Unable to set selection divider", e);
     97                 sTrySelectionDivider = false;
     98             }
     99         }
    100     }
    101 
    102     /**
    103      * @return the state of this NumberPicker including the currently selected value
    104      */
    105     @Override
    106     protected Parcelable onSaveInstanceState() {
    107         return new State(super.onSaveInstanceState(), getValue());
    108     }
    109 
    110     /**
    111      * @param state the state of this NumberPicker including the value to select
    112      */
    113     @Override
    114     protected void onRestoreInstanceState(Parcelable state) {
    115         final State instanceState = (State) state;
    116         super.onRestoreInstanceState(instanceState.getSuperState());
    117         setValue(instanceState.mValue);
    118     }
    119 
    120     @Override
    121     public void setOnValueChangedListener(OnValueChangeListener onValueChangedListener) {
    122         mOnValueChangedListener = onValueChangedListener;
    123     }
    124 
    125     @Override
    126     public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    127         if (mOnValueChangedListener != null) {
    128             mOnValueChangedListener.onValueChange(picker, oldVal, newVal);
    129         }
    130 
    131         // Wait till we reach a value to prevent TalkBack from announcing every intermediate value
    132         // when scrolling fast.
    133         removeCallbacks(mAnnounceValueRunnable);
    134         postDelayed(mAnnounceValueRunnable, 200L);
    135     }
    136 
    137     /**
    138      * Register a callback to be invoked whenever a value change should be announced.
    139      */
    140     public void setOnAnnounceValueChangedListener(OnAnnounceValueChangedListener listener) {
    141         mOnAnnounceValueChangedListener = listener;
    142     }
    143 
    144     /**
    145      * The state of this NumberPicker including the selected value. Used to preserve values across
    146      * device rotation.
    147      */
    148     private static final class State extends BaseSavedState {
    149 
    150         private final int mValue;
    151 
    152         public State(Parcel source) {
    153             super(source);
    154             mValue = source.readInt();
    155         }
    156 
    157         public State(Parcelable superState, int value) {
    158             super(superState);
    159             mValue = value;
    160         }
    161 
    162         @Override
    163         public void writeToParcel(Parcel dest, int flags) {
    164             super.writeToParcel(dest, flags);
    165             dest.writeInt(mValue);
    166         }
    167 
    168         public static final Parcelable.Creator<State> CREATOR =
    169                 new Parcelable.Creator<State>() {
    170                     public State createFromParcel(Parcel in) { return new State(in); }
    171                     public State[] newArray(int size) { return new State[size]; }
    172                 };
    173     }
    174 
    175     /**
    176      * Interface for a callback to be invoked when a value change should be announced for
    177      * accessibility.
    178      */
    179     public interface OnAnnounceValueChangedListener {
    180         /**
    181          * Called when a value change should be announced.
    182          * @param picker The number picker whose value changed.
    183          * @param value The new value.
    184          * @param displayedValue The text displayed for the value, or null if the value itself
    185          *     is displayed.
    186          */
    187         void onAnnounceValueChanged(NumberPicker picker, int value, String displayedValue);
    188     }
    189 }