TimePickerSpinnerDelegate.java revision 67945c11a5e9547f71be91ceb99e7b9ff15a6292
15f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer/*
25f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * Copyright (C) 2013 The Android Open Source Project
35f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer *
45f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * Licensed under the Apache License, Version 2.0 (the "License");
55f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * you may not use this file except in compliance with the License.
65f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * You may obtain a copy of the License at
75f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer *
85f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer *      http://www.apache.org/licenses/LICENSE-2.0
95f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer *
105f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * Unless required by applicable law or agreed to in writing, software
115f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * distributed under the License is distributed on an "AS IS" BASIS,
125f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * See the License for the specific language governing permissions and
145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * limitations under the License.
155f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer */
165f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
177573098b83e780d1c5bea13b384b610d8f155676Steve Naroffpackage android.widget;
185f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
199caf8b1ca6beb254f420dada3c0e94d5ef027f58Ted Kremenekimport android.content.Context;
205f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.content.res.ColorStateList;
2177ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenekimport android.content.res.Configuration;
220a449eed1dd2439b4b9c0a6291084816eab390c1Ted Kremenekimport android.content.res.Resources;
235f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.content.res.TypedArray;
245f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.os.Parcel;
257573098b83e780d1c5bea13b384b610d8f155676Steve Naroffimport android.os.Parcelable;
267573098b83e780d1c5bea13b384b610d8f155676Steve Naroffimport android.text.TextUtils;
275f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.text.format.DateFormat;
285f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.text.format.DateUtils;
295f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.util.AttributeSet;
308e74c93ddaa8268a999e1b25c723dc1984a434b4Steve Naroffimport android.util.Log;
315f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.util.TypedValue;
320c727a35718556866a978f64ac549d9798735f08Chris Lattnerimport android.view.HapticFeedbackConstants;
336a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlssonimport android.view.KeyCharacterMap;
346c36be5b383875b490684bcf439d6d427298c1afChris Lattnerimport android.view.KeyEvent;
3542a509f6a4f71bb805cc4abbb26722a34dffdddeTed Kremenekimport android.view.LayoutInflater;
3642a509f6a4f71bb805cc4abbb26722a34dffdddeTed Kremenekimport android.view.View;
375f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.view.ViewGroup;
385f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.view.accessibility.AccessibilityEvent;
395f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.view.accessibility.AccessibilityNodeInfo;
405f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
415f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport com.android.internal.R;
425f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
435f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport java.util.ArrayList;
445f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport java.util.Calendar;
455f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport java.util.Locale;
465f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
475f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer/**
485f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * A delegate implementing the radial clock-based TimePicker.
495f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer */
505f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerclass TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate implements
515f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        RadialTimePickerView.OnValueSelectedListener {
525f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
535f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static final String TAG = "TimePickerDelegate";
545f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
555f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    // Index used by RadialPickerLayout
565f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static final int HOUR_INDEX = 0;
575f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static final int MINUTE_INDEX = 1;
585f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
59b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    // NOT a real index for the purpose of what's showing.
60b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    private static final int AMPM_INDEX = 2;
61b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
62b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    // Also NOT a real index, just used for keyboard mode.
63b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    private static final int ENABLE_PICKER_INDEX = 3;
64311ff02fae0392bee6abe7723cdf5a69b2899a47Chris Lattner
65311ff02fae0392bee6abe7723cdf5a69b2899a47Chris Lattner    static final int AM = 0;
665f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    static final int PM = 1;
675f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
685f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static final boolean DEFAULT_ENABLED_STATE = true;
695f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
705f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
716000dace22f110d8768476989313e9d981d690d0Chris Lattner    private static final int HOURS_IN_HALF_DAY = 12;
726000dace22f110d8768476989313e9d981d690d0Chris Lattner
736000dace22f110d8768476989313e9d981d690d0Chris Lattner    private final View mHeaderView;
746000dace22f110d8768476989313e9d981d690d0Chris Lattner    private final TextView mHourView;
755f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private final TextView mMinuteView;
76e300c870f08d08badf2ebcb53ded49f304af37fcChris Lattner    private final View mAmPmLayout;
776000dace22f110d8768476989313e9d981d690d0Chris Lattner    private final CheckedTextView mAmLabel;
786000dace22f110d8768476989313e9d981d690d0Chris Lattner    private final CheckedTextView mPmLabel;
796000dace22f110d8768476989313e9d981d690d0Chris Lattner    private final RadialTimePickerView mRadialTimePickerView;
80e300c870f08d08badf2ebcb53ded49f304af37fcChris Lattner    private final TextView mSeparatorView;
816000dace22f110d8768476989313e9d981d690d0Chris Lattner
826000dace22f110d8768476989313e9d981d690d0Chris Lattner    private final String mAmText;
836000dace22f110d8768476989313e9d981d690d0Chris Lattner    private final String mPmText;
846000dace22f110d8768476989313e9d981d690d0Chris Lattner
8542a509f6a4f71bb805cc4abbb26722a34dffdddeTed Kremenek    private final float mDisabledAlpha;
865f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
87d2a4a1af1088fca80e2dc76eb3369db0fbbfdefdTed Kremenek    private boolean mAllowAutoAdvance;
88d2a4a1af1088fca80e2dc76eb3369db0fbbfdefdTed Kremenek    private int mInitialHourOfDay;
89d2a4a1af1088fca80e2dc76eb3369db0fbbfdefdTed Kremenek    private int mInitialMinute;
90d2a4a1af1088fca80e2dc76eb3369db0fbbfdefdTed Kremenek    private boolean mIs24HourView;
915f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
928297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    // For hardware IME input.
9377ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private char mPlaceholderText;
94d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private String mDoublePlaceholderText;
95d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private String mDeletedKeyFormat;
96d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private boolean mInKbMode;
97d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private ArrayList<Integer> mTypedTimes = new ArrayList<Integer>();
98d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private Node mLegalTimesTree;
99d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private int mAmKeyCode;
100d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private int mPmKeyCode;
10177ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek
10277ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    // Accessibility strings.
10377ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private String mHourPickerDescription;
1049caf8b1ca6beb254f420dada3c0e94d5ef027f58Ted Kremenek    private String mSelectHours;
1059caf8b1ca6beb254f420dada3c0e94d5ef027f58Ted Kremenek    private String mMinutePickerDescription;
10677ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private String mSelectMinutes;
1078297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
1088297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    private Calendar mTempCalendar;
10977ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek
11077ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    public TimePickerSpinnerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
1119caf8b1ca6beb254f420dada3c0e94d5ef027f58Ted Kremenek            int defStyleAttr, int defStyleRes) {
11277ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek        super(delegator, context);
11377ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek
11477ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek        // process style attributes
1159caf8b1ca6beb254f420dada3c0e94d5ef027f58Ted Kremenek        final TypedArray a = mContext.obtainStyledAttributes(attrs,
11677ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek                R.styleable.TimePicker, defStyleAttr, defStyleRes);
1172dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek        final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
1182dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek                Context.LAYOUT_INFLATER_SERVICE);
1190f84c0059cec39fd1c73ac05bc2864dca664e7f4Ted Kremenek        final Resources res = mContext.getResources();
1202dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek
121ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        mHourPickerDescription = res.getString(R.string.hour_picker_description);
1222dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek        mSelectHours = res.getString(R.string.select_hours);
1232dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek        mMinutePickerDescription = res.getString(R.string.minute_picker_description);
1242dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek        mSelectMinutes = res.getString(R.string.select_minutes);
1255f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1265f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        String[] amPmStrings = TimePickerClockDelegate.getAmPmStrings(context);
1275f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mAmText = amPmStrings[0];
1285f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mPmText = amPmStrings[1];
1295f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1305f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
1315f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                R.layout.time_picker_holo);
1325f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final View mainView = inflater.inflate(layoutResourceId, delegator);
1338e74c93ddaa8268a999e1b25c723dc1984a434b4Steve Naroff
1345f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mHeaderView = mainView.findViewById(R.id.time_header);
1358e74c93ddaa8268a999e1b25c723dc1984a434b4Steve Naroff        mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
1365f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1378e74c93ddaa8268a999e1b25c723dc1984a434b4Steve Naroff        // Set up hour/minute labels.
1388e74c93ddaa8268a999e1b25c723dc1984a434b4Steve Naroff        mHourView = (TextView) mHeaderView.findViewById(R.id.hours);
139b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mHourView.setOnClickListener(mClickListener);
140b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mSeparatorView = (TextView) mHeaderView.findViewById(R.id.separator);
1415f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mMinuteView = (TextView) mHeaderView.findViewById(R.id.minutes);
1425f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mMinuteView.setOnClickListener(mClickListener);
1435f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1445f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final int headerTimeTextAppearance = a.getResourceId(
1455f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                R.styleable.TimePicker_headerTimeTextAppearance, 0);
1468297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        if (headerTimeTextAppearance != 0) {
1478297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mHourView.setTextAppearance(context, headerTimeTextAppearance);
1488297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mSeparatorView.setTextAppearance(context, headerTimeTextAppearance);
1498297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mMinuteView.setTextAppearance(context, headerTimeTextAppearance);
1500965f446e7685dc01fc0b5e718610530eed3cc63Ted Kremenek        }
151ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek
152ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        // TODO: This can be removed once we support themed color state lists.
1535f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final int headerSelectedTextColor = a.getColor(
1545f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                R.styleable.TimePicker_headerSelectedTextColor,
1555f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                res.getColor(R.color.timepicker_default_selector_color_material));
1565f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mHourView.setTextColor(ColorStateList.addFirstIfMissing(mHourView.getTextColors(),
1575f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                R.attr.state_selected, headerSelectedTextColor));
1585f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mMinuteView.setTextColor(ColorStateList.addFirstIfMissing(mMinuteView.getTextColors(),
1595f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                R.attr.state_selected, headerSelectedTextColor));
1605f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1615f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // Set up AM/PM labels.
1625f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mAmPmLayout = mHeaderView.findViewById(R.id.ampm_layout);
163b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label);
164507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff        mAmLabel.setText(amPmStrings[0]);
1655f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mAmLabel.setOnClickListener(mClickListener);
1665f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label);
1675f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mPmLabel.setText(amPmStrings[1]);
1685f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mPmLabel.setOnClickListener(mClickListener);
1695f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1708297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        final int headerAmPmTextAppearance = a.getResourceId(
1718297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                R.styleable.TimePicker_headerAmPmTextAppearance, 0);
1728297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        if (headerAmPmTextAppearance != 0) {
1738297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mAmLabel.setTextAppearance(context, headerAmPmTextAppearance);
1744927be6102784fc69c50234f7e2253283b3e99e4Ted Kremenek            mPmLabel.setTextAppearance(context, headerAmPmTextAppearance);
175ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        }
176ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek
1775f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        a.recycle();
1785f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1795f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // Pull disabled alpha from theme.
1805f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final TypedValue outValue = new TypedValue();
1815f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
1825f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mDisabledAlpha = outValue.getFloat();
183b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
1845f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById(
185b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                R.id.radial_picker);
186b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
187b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        setupListeners();
188b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
18954395d440dc82a5e51b945c6c2a7f4bc4bea0358Ted Kremenek        mAllowAutoAdvance = true;
190ab18c4c0ac1a46a38aa84c2c8ea485612e21a614Chris Lattner
191ab18c4c0ac1a46a38aa84c2c8ea485612e21a614Chris Lattner        // Set up for keyboard mode.
1925f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mDoublePlaceholderText = res.getString(R.string.time_placeholder);
1935f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mDeletedKeyFormat = res.getString(R.string.deleted_key);
1945f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mPlaceholderText = mDoublePlaceholderText.charAt(0);
195ab18c4c0ac1a46a38aa84c2c8ea485612e21a614Chris Lattner        mAmKeyCode = mPmKeyCode = -1;
1965f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        generateLegalTimesTree();
1975f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1985f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // Initialize with current time
1995f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final Calendar calendar = Calendar.getInstance(mCurrentLocale);
200ab18c4c0ac1a46a38aa84c2c8ea485612e21a614Chris Lattner        final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
2014ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek        final int currentMinute = calendar.get(Calendar.MINUTE);
2024ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek        initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX);
2034ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek    }
2044ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek
2054ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek    private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
2064ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek        mInitialHourOfDay = hourOfDay;
2074ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek        mInitialMinute = minute;
2084ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek        mIs24HourView = is24HourView;
2094ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek        mInKbMode = false;
2104ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek        updateUI(index);
2115f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
212b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
213b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    private void setupListeners() {
214b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mHeaderView.setOnKeyListener(mKeyListener);
215b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mHeaderView.setOnFocusChangeListener(mFocusListener);
21654395d440dc82a5e51b945c6c2a7f4bc4bea0358Ted Kremenek        mHeaderView.setFocusable(true);
217cc326204dd97771c336b9aab3b9963ea30d69c29Ted Kremenek
218cc326204dd97771c336b9aab3b9963ea30d69c29Ted Kremenek        mRadialTimePickerView.setOnValueSelectedListener(this);
21954395d440dc82a5e51b945c6c2a7f4bc4bea0358Ted Kremenek    }
2205f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
2215f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private void updateUI(int index) {
2225f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // Update RadialPicker values
2235f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        updateRadialPicker(index);
2248297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // Enable or disable the AM/PM view.
2258297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        updateHeaderAmPm();
2268297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // Update Hour and Minutes
2278297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        updateHeaderHour(mInitialHourOfDay, true);
2282dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek        // Update time separator
229ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        updateHeaderSeparator();
230ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        // Update Minutes
2315f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        updateHeaderMinute(mInitialMinute);
2325f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // Invalidate everything
233c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        mDelegator.invalidate();
234c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson    }
235103fc81f12aa635aa0a573c94b1aceb496b4e587Ted Kremenek
236c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson    private void updateRadialPicker(int index) {
237c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView);
238c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        setCurrentItemShowing(index, false, true);
239103fc81f12aa635aa0a573c94b1aceb496b4e587Ted Kremenek    }
240d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek
241c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson    private int computeMaxWidthOfNumbers(int max) {
242c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        TextView tempView = new TextView(mContext);
243c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        tempView.setTextAppearance(mContext, R.style.TextAppearance_Material_TimePicker_TimeLabel);
244c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
245c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                ViewGroup.LayoutParams.WRAP_CONTENT);
246c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        tempView.setLayoutParams(lp);
247c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        int maxWidth = 0;
248d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        for (int minutes = 0; minutes < max; minutes++) {
249d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek            final String text = String.format("%02d", minutes);
250d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek            tempView.setText(text);
251b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            tempView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
252b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            maxWidth = Math.max(maxWidth, tempView.getMeasuredWidth());
253edcc752060be38bfa7e7b32691c0cc4d843622a8Ted Kremenek        }
254c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        return maxWidth;
255c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson    }
256c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson
257c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson    private void updateHeaderAmPm() {
258c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        if (mIs24HourView) {
259c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson            mAmPmLayout.setVisibility(View.GONE);
260c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        } else {
261c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson            final String bestDateTimePattern = DateFormat.getBestDateTimePattern(
262d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                    mCurrentLocale, "hm");
263d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek            boolean amPmOnLeft = bestDateTimePattern.startsWith("a");
264d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek            if (TextUtils.getLayoutDirectionFromLocale(mCurrentLocale) ==
265b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                    View.LAYOUT_DIRECTION_RTL) {
2665f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                amPmOnLeft = !amPmOnLeft;
267b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            }
268d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek
269d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek            final ViewGroup.MarginLayoutParams params =
270d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                    (ViewGroup.MarginLayoutParams) mAmPmLayout.getLayoutParams();
271d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek
272b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            if (amPmOnLeft) {
273d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                params.leftMargin = 0;
2745f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                params.rightMargin = computeMaxWidthOfNumbers(12 /* for hours */);
275764a7ce5217f9569e100a3445f47496ee82daf86Chris Lattner            } else {
276764a7ce5217f9569e100a3445f47496ee82daf86Chris Lattner                params.leftMargin = computeMaxWidthOfNumbers(60 /* for minutes */);
277d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                params.rightMargin = 0;
278d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek            }
279d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek
280d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek            mAmPmLayout.setLayoutParams(params);
28151b09f2c528c8460b5465c676173324e44176d62Devang Patel            mAmPmLayout.setVisibility(View.VISIBLE);
28251b09f2c528c8460b5465c676173324e44176d62Devang Patel
28351b09f2c528c8460b5465c676173324e44176d62Devang Patel            updateAmPmLabelStates(mInitialHourOfDay < 12 ? AM : PM);
28451b09f2c528c8460b5465c676173324e44176d62Devang Patel        }
28551b09f2c528c8460b5465c676173324e44176d62Devang Patel    }
28651b09f2c528c8460b5465c676173324e44176d62Devang Patel
28751b09f2c528c8460b5465c676173324e44176d62Devang Patel    /**
2885f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * Set the current hour.
289b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff     */
290b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    @Override
291b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    public void setCurrentHour(Integer currentHour) {
2925f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        if (mInitialHourOfDay == currentHour) {
2935f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            return;
2945f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
2955f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mInitialHourOfDay = currentHour;
296d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        updateHeaderHour(currentHour, true /* accessibility announce */);
297d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        updateHeaderAmPm();
298d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        mRadialTimePickerView.setCurrentHour(currentHour);
299d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
3009eea2ca5f2cb5d77569274702b5b06273e426dc2Ted Kremenek        mDelegator.invalidate();
301ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        onTimeChanged();
302ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    }
3035f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
3045f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
305c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson     * @return The current hour in the range (0-23).
306d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek     */
3076c36be5b383875b490684bcf439d6d427298c1afChris Lattner    @Override
3085f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    public Integer getCurrentHour() {
309edcc752060be38bfa7e7b32691c0cc4d843622a8Ted Kremenek        int currentHour = mRadialTimePickerView.getCurrentHour();
310d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        if (mIs24HourView) {
311b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            return currentHour;
312d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        } else {
313d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek            switch(mRadialTimePickerView.getAmOrPm()) {
31451b09f2c528c8460b5465c676173324e44176d62Devang Patel                case PM:
315d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                    return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
3166c36be5b383875b490684bcf439d6d427298c1afChris Lattner                case AM:
3175f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                default:
318b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                    return currentHour % HOURS_IN_HALF_DAY;
319b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            }
320b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        }
3215f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
3225f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
3235f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
3245f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * Set the current minute (0-59).
325d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek     */
326d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek    @Override
327d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek    public void setCurrentMinute(Integer currentMinute) {
328d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        if (mInitialMinute == currentMinute) {
3299eea2ca5f2cb5d77569274702b5b06273e426dc2Ted Kremenek            return;
330ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        }
331ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        mInitialMinute = currentMinute;
3325f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        updateHeaderMinute(currentMinute);
3335f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mRadialTimePickerView.setCurrentMinute(currentMinute);
3345f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mDelegator.invalidate();
3355f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        onTimeChanged();
3365f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
337b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
3385f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
339b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff     * @return The current minute.
340b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff     */
341b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    @Override
3425f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    public Integer getCurrentMinute() {
3435f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        return mRadialTimePickerView.getCurrentMinute();
3445f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
3455f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
3465f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
3475f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * Set whether in 24 hour or AM/PM mode.
3485f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     *
3495f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * @param is24HourView True = 24 hour mode. False = AM/PM.
3505f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
351b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    @Override
352b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    public void setIs24HourView(Boolean is24HourView) {
353b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        if (is24HourView == mIs24HourView) {
354b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            return;
3555f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
3565f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mIs24HourView = is24HourView;
3575f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        generateLegalTimesTree();
3585f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        int hour = mRadialTimePickerView.getCurrentHour();
3598297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mInitialHourOfDay = hour;
3608297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        updateHeaderHour(hour, false /* no accessibility announce */);
3618297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        updateHeaderAmPm();
3628297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing());
363b15132fda52046ba65eafd6f4b448f7aa16b7e8dTed Kremenek        mDelegator.invalidate();
364ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    }
365ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek
3665f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
3675f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * @return true if this is in 24 hour view else false.
3685f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
3695f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    @Override
3705f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    public boolean is24HourView() {
3715f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        return mIs24HourView;
3728297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
3738297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
374b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    @Override
3755f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener callback) {
376b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mOnTimeChangedListener = callback;
377b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    }
3788297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
3798297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    @Override
3808297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    public void setEnabled(boolean enabled) {
381b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mHourView.setEnabled(enabled);
3828297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mMinuteView.setEnabled(enabled);
3835f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mAmLabel.setEnabled(enabled);
3848297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mPmLabel.setEnabled(enabled);
3858297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mRadialTimePickerView.setEnabled(enabled);
3868297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mIsEnabled = enabled;
3875f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
3888297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
3898297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    @Override
3908297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    public boolean isEnabled() {
391b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        return mIsEnabled;
392b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    }
393b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
394b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    @Override
395b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    public int getBaseline() {
396b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        // does not support baseline alignment
397b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        return -1;
3985f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
3995f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
4005f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    @Override
4015f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    public void onConfigurationChanged(Configuration newConfig) {
4025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        updateUI(mRadialTimePickerView.getCurrentItemShowing());
4038297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
4048297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
4058297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    @Override
4068297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    public Parcelable onSaveInstanceState(Parcelable superState) {
4074210f3dfd73ad3482f9cfa0a382e1fd78f22976dTed Kremenek        return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
408ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek                is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing());
409ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    }
4105f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
4115f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    @Override
4125f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    public void onRestoreInstanceState(Parcelable state) {
4135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        SavedState ss = (SavedState) state;
4145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        setInKbMode(ss.inKbMode());
4158297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        setTypedTimes(ss.getTypesTimes());
4168297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing());
417c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        mRadialTimePickerView.invalidate();
418c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        if (mInKbMode) {
4199dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff            tryStartingKbMode(-1);
4205f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mHourView.invalidate();
4218297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        }
4228297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
4238297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
4248297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    @Override
4255f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    public void setCurrentLocale(Locale locale) {
4268297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        super.setCurrentLocale(locale);
4278297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mTempCalendar = Calendar.getInstance(locale);
428c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson    }
429c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson
4308297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    @Override
4318297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
432c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        onPopulateAccessibilityEvent(event);
433c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        return true;
4349dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff    }
4359dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff
4369dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff    @Override
4379dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
438c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        int flags = DateUtils.FORMAT_SHOW_TIME;
439c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        if (mIs24HourView) {
440c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson            flags |= DateUtils.FORMAT_24HOUR;
441c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        } else {
442c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson            flags |= DateUtils.FORMAT_12HOUR;
443c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        }
4449dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff        mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
4459dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff        mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
4469dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff        String selectedDate = DateUtils.formatDateTime(mContext,
4475f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                mTempCalendar.getTimeInMillis(), flags);
4485f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        event.getText().add(selectedDate);
4495f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
4505f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
4518297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    @Override
4528297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
4538297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        event.setClassName(TimePicker.class.getName());
4548297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
4559eea2ca5f2cb5d77569274702b5b06273e426dc2Ted Kremenek
456ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    @Override
457ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
4585f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        info.setClassName(TimePicker.class.getName());
4595f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
4605f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
4615f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
4625f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * Set whether in keyboard mode or not.
4635f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     *
4648297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek     * @param inKbMode True means in keyboard mode.
4658297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek     */
466b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    private void setInKbMode(boolean inKbMode) {
4675f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mInKbMode = inKbMode;
468b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    }
4698297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
4708297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    /**
471b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff     * @return true if in keyboard mode
4728297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek     */
4735f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private boolean inKbMode() {
4748297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        return mInKbMode;
4758297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
4768297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
4778297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    private void setTypedTimes(ArrayList<Integer> typeTimes) {
478b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mTypedTimes = typeTimes;
479b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    }
480b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
481b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    /**
4825f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * @return an array of typed times
4835f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
4845f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private ArrayList<Integer> getTypedTimes() {
4855f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        return mTypedTimes;
4868297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
4878297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
4888297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    /**
4898297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek     * @return the index of the current item showing
4905572b94b76a78a35bac32d1ef04907c7a651adbaTed Kremenek     */
491ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    private int getCurrentItemShowing() {
492ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        return mRadialTimePickerView.getCurrentItemShowing();
4935f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
4945f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
4955f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
4965f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * Propagate the time change
4975f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
4988297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    private void onTimeChanged() {
4998297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
500b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        if (mOnTimeChangedListener != null) {
5015f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mOnTimeChangedListener.onTimeChanged(mDelegator,
502b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                    getCurrentHour(), getCurrentMinute());
503b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        }
5048297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
5058297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
506b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    /**
5078297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek     * Used to save / restore state of time picker
5085f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
5098297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    private static class SavedState extends View.BaseSavedState {
5108297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
5118297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        private final int mHour;
5128297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        private final int mMinute;
513b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        private final boolean mIs24HourMode;
514b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        private final boolean mInKbMode;
515b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        private final ArrayList<Integer> mTypedTimes;
516b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        private final int mCurrentItemShowing;
5175f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5185f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode,
5195f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                           boolean isKbMode, ArrayList<Integer> typedTimes,
5205f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                           int currentItemShowing) {
5218297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            super(superState);
5228297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mHour = hour;
5238297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mMinute = minute;
5248297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mIs24HourMode = is24HourMode;
525e3299ef345bb8a71901108bf83a824591eae6c1aTed Kremenek            mInKbMode = isKbMode;
526ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            mTypedTimes = typedTimes;
527ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            mCurrentItemShowing = currentItemShowing;
5285f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
5295f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5305f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        private SavedState(Parcel in) {
5315f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            super(in);
5325f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mHour = in.readInt();
5335f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mMinute = in.readInt();
5345f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mIs24HourMode = (in.readInt() == 1);
5355f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mInKbMode = (in.readInt() == 1);
5368297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mTypedTimes = in.readArrayList(getClass().getClassLoader());
5378297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mCurrentItemShowing = in.readInt();
538b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        }
5395f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
540b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        public int getHour() {
541b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            return mHour;
542af458c9f658335bdea482395ff4a25c75935e129Chris Lattner        }
543af458c9f658335bdea482395ff4a25c75935e129Chris Lattner
544af458c9f658335bdea482395ff4a25c75935e129Chris Lattner        public int getMinute() {
545af458c9f658335bdea482395ff4a25c75935e129Chris Lattner            return mMinute;
546b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        }
547af458c9f658335bdea482395ff4a25c75935e129Chris Lattner
5485f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        public boolean is24HourMode() {
5498297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            return mIs24HourMode;
5508297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        }
5518297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
5528297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        public boolean inKbMode() {
5538297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            return mInKbMode;
5548297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        }
5558297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
5568297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        public ArrayList<Integer> getTypesTimes() {
5578297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            return mTypedTimes;
558b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        }
559b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
560b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        public int getCurrentItemShowing() {
561b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            return mCurrentItemShowing;
5625f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
5635f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5645f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        @Override
5655f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        public void writeToParcel(Parcel dest, int flags) {
5668297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            super.writeToParcel(dest, flags);
5678297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            dest.writeInt(mHour);
5688297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            dest.writeInt(mMinute);
5698297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            dest.writeInt(mIs24HourMode ? 1 : 0);
57007ba046c7d7bbc1f42b86b3ed012eec8fc7849daTed Kremenek            dest.writeInt(mInKbMode ? 1 : 0);
571ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            dest.writeList(mTypedTimes);
572ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            dest.writeInt(mCurrentItemShowing);
5735f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
5745f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5755f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        @SuppressWarnings({"unused", "hiding"})
5765f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
5775f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            public SavedState createFromParcel(Parcel in) {
5785f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                return new SavedState(in);
579507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff            }
58061f62165220e75694fe333179c78815e2e48d71fTed Kremenek
5815f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            public SavedState[] newArray(int size) {
58261f62165220e75694fe333179c78815e2e48d71fTed Kremenek                return new SavedState[size];
58361f62165220e75694fe333179c78815e2e48d71fTed Kremenek            }
5845f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        };
5855f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
586b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
587507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff    private void tryVibrate() {
58861f62165220e75694fe333179c78815e2e48d71fTed Kremenek        mDelegator.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
589507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff    }
5905f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5915f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private void updateAmPmLabelStates(int amOrPm) {
5925f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final boolean isAm = amOrPm == AM;
5935f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mAmLabel.setChecked(isAm);
5948297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mAmLabel.setAlpha(isAm ? 1 : mDisabledAlpha);
5958297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
5968297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        final boolean isPm = amOrPm == PM;
5978297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mPmLabel.setChecked(isPm);
5983f0767b74b16b37bcce60ff457609161a0665784Ted Kremenek        mPmLabel.setAlpha(isPm ? 1 : mDisabledAlpha);
599ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    }
600ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek
6015f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
6025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * Called by the picker for updating the header display.
6035f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
6045f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    @Override
6055f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) {
6065f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        if (pickerIndex == HOUR_INDEX) {
607225a2d946f73aed3b38f265fcb4e7d7d88136928Ted Kremenek            updateHeaderHour(newValue, false);
608225a2d946f73aed3b38f265fcb4e7d7d88136928Ted Kremenek            String announcement = String.format("%d", newValue);
6095f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            if (mAllowAutoAdvance && autoAdvance) {
6106000dace22f110d8768476989313e9d981d690d0Chris Lattner                setCurrentItemShowing(MINUTE_INDEX, true, false);
6115f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                announcement += ". " + mSelectMinutes;
6125f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            } else {
6136000dace22f110d8768476989313e9d981d690d0Chris Lattner                mRadialTimePickerView.setContentDescription(
614b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                        mHourPickerDescription + ": " + newValue);
615b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            }
6165f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
6175f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mRadialTimePickerView.announceForAccessibility(announcement);
6185f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        } else if (pickerIndex == MINUTE_INDEX){
6195f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            updateHeaderMinute(newValue);
6205f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + newValue);
6218297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        } else if (pickerIndex == AMPM_INDEX) {
6228297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            updateAmPmLabelStates(newValue);
6238297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        } else if (pickerIndex == ENABLE_PICKER_INDEX) {
6248297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            if (!isTypedTimeFullyLegal()) {
625225a2d946f73aed3b38f265fcb4e7d7d88136928Ted Kremenek                mTypedTimes.clear();
626ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            }
627ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            finishKbMode();
6285f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
6295f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
6305f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
6315f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private void updateHeaderHour(int value, boolean announce) {
6325f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
6335f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                (mIs24HourView) ? "Hm" : "hm");
634507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff        final int lengthPattern = bestDateTimePattern.length();
6355f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        boolean hourWithTwoDigit = false;
636507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff        char hourFormat = '\0';
637b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
638507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff        // the hour format that we found.
639507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff        for (int i = 0; i < lengthPattern; i++) {
640507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff            final char c = bestDateTimePattern.charAt(i);
6415f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
6425f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                hourFormat = c;
6435f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
6445f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                    hourWithTwoDigit = true;
6458297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                }
6468297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                break;
6478297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            }
6488297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        }
64996f22423c52c22d263b4e5f7ee762122b607459eTed Kremenek        final String format;
650ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        if (hourWithTwoDigit) {
651ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            format = "%02d";
6525f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        } else {
6535f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            format = "%d";
6545f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
6555f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        if (mIs24HourView) {
6565f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            // 'k' means 1-24 hour
657507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff            if (hourFormat == 'k' && value == 0) {
6585f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                value = 24;
659507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff            }
660b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        } else {
661507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff            // 'K' means 0-11 hour
662b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            value = modulo12(value, hourFormat == 'K');
6635f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
6645f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        CharSequence text = String.format(format, value);
6655f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mHourView.setText(text);
6665f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        if (announce) {
6678297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mRadialTimePickerView.announceForAccessibility(text);
6688297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        }
6698297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
6708297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
6719eea2ca5f2cb5d77569274702b5b06273e426dc2Ted Kremenek    private static int modulo12(int n, boolean startWithZero) {
672ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        int value = n % 12;
673ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        if (value == 0 && !startWithZero) {
6745f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            value = 12;
6755f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
6765f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        return value;
6775f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
6785f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
6795f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
6805f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
681507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff     *
6825f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * See http://unicode.org/cldr/trac/browser/trunk/common/main
683507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff     *
684507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff     * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
6855f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * separator as the character which is just after the hour marker in the returned pattern.
6865f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
6875f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private void updateHeaderSeparator() {
688b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
689507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff                (mIs24HourView) ? "Hm" : "hm");
6905f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final String separatorText;
6915f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats
6925f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final char[] hourFormats = {'H', 'h', 'K', 'k'};
6935f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        int hIndex = lastIndexOfAny(bestDateTimePattern, hourFormats);
6945f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        if (hIndex == -1) {
6958297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            // Default case
6968297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            separatorText = ":";
6978297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        } else {
6988297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1));
6992dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek        }
700ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        mSeparatorView.setText(separatorText);
701ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    }
7025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
7035f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    static private int lastIndexOfAny(String str, char[] any) {
704fe795956194141c91ae555985c9b930595bff43fChris Lattner        final int lengthAny = any.length;
705fe795956194141c91ae555985c9b930595bff43fChris Lattner        if (lengthAny > 0) {
706fe795956194141c91ae555985c9b930595bff43fChris Lattner            for (int i = str.length() - 1; i >= 0; i--) {
707fe795956194141c91ae555985c9b930595bff43fChris Lattner                char c = str.charAt(i);
7086a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlsson                for (int j = 0; j < lengthAny; j++) {
709fe795956194141c91ae555985c9b930595bff43fChris Lattner                    if (c == any[j]) {
710fe795956194141c91ae555985c9b930595bff43fChris Lattner                        return i;
7116a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlsson                    }
7126a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlsson                }
7136a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlsson            }
7146a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlsson        }
715fe795956194141c91ae555985c9b930595bff43fChris Lattner        return -1;
7166a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlsson    }
7176a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlsson
7186a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlsson    private void updateHeaderMinute(int value) {
719fe795956194141c91ae555985c9b930595bff43fChris Lattner        if (value == 60) {
720fe795956194141c91ae555985c9b930595bff43fChris Lattner            value = 0;
721fe795956194141c91ae555985c9b930595bff43fChris Lattner        }
722fe795956194141c91ae555985c9b930595bff43fChris Lattner        CharSequence text = String.format(mCurrentLocale, "%02d", value);
723fe795956194141c91ae555985c9b930595bff43fChris Lattner        mRadialTimePickerView.announceForAccessibility(text);
724fe795956194141c91ae555985c9b930595bff43fChris Lattner        mMinuteView.setText(text);
725fe795956194141c91ae555985c9b930595bff43fChris Lattner    }
726fe795956194141c91ae555985c9b930595bff43fChris Lattner
727fe795956194141c91ae555985c9b930595bff43fChris Lattner    /**
7281f85acd5ff54345788b93111197088160fe9f06eTed Kremenek     * Show either Hours or Minutes.
7291f85acd5ff54345788b93111197088160fe9f06eTed Kremenek     */
7301f85acd5ff54345788b93111197088160fe9f06eTed Kremenek    private void setCurrentItemShowing(int index, boolean animateCircle, boolean announce) {
731fe795956194141c91ae555985c9b930595bff43fChris Lattner        mRadialTimePickerView.setCurrentItemShowing(index, animateCircle);
732b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian
733b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        if (index == HOUR_INDEX) {
734b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            int hours = mRadialTimePickerView.getCurrentHour();
735b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            if (!mIs24HourView) {
736b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                hours = hours % 12;
737b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            }
7383b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian            mRadialTimePickerView.setContentDescription(mHourPickerDescription + ": " + hours);
7393b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian            if (announce) {
740b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                mRadialTimePickerView.announceForAccessibility(mSelectHours);
7413b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian            }
742b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        } else {
743b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            int minutes = mRadialTimePickerView.getCurrentMinute();
7443b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian            mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + minutes);
7457573098b83e780d1c5bea13b384b610d8f155676Steve Naroff            if (announce) {
7463b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian                mRadialTimePickerView.announceForAccessibility(mSelectMinutes);
7473b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian            }
7483b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian        }
7493b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian
7503b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian        mHourView.setSelected(index == HOUR_INDEX);
7513b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian        mMinuteView.setSelected(index == MINUTE_INDEX);
7523b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian    }
7533b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian
7543b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian    private void setAmOrPm(int amOrPm) {
7553b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian        updateAmPmLabelStates(amOrPm);
7563b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian        mRadialTimePickerView.setAmOrPm(amOrPm);
7577573098b83e780d1c5bea13b384b610d8f155676Steve Naroff    }
7587573098b83e780d1c5bea13b384b610d8f155676Steve Naroff
7593b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian    /**
760b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian     * For keyboard mode, processes key events.
761606efdfeb9eef70871eb62a26dfd115e209e899cFariborz Jahanian     *
762606efdfeb9eef70871eb62a26dfd115e209e899cFariborz Jahanian     * @param keyCode the pressed key.
7637573098b83e780d1c5bea13b384b610d8f155676Steve Naroff     *
7647573098b83e780d1c5bea13b384b610d8f155676Steve Naroff     * @return true if the key was successfully processed, false otherwise.
765606efdfeb9eef70871eb62a26dfd115e209e899cFariborz Jahanian     */
766606efdfeb9eef70871eb62a26dfd115e209e899cFariborz Jahanian    private boolean processKeyUp(int keyCode) {
7677794cb85d394750db0631c02b7aa7837ae56764cFariborz Jahanian        if (keyCode == KeyEvent.KEYCODE_DEL) {
7687573098b83e780d1c5bea13b384b610d8f155676Steve Naroff            if (mInKbMode) {
7697573098b83e780d1c5bea13b384b610d8f155676Steve Naroff                if (!mTypedTimes.isEmpty()) {
770b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                    int deleted = deleteLastTypedKey();
7713b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian                    String deletedKeyStr;
772b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                    if (deleted == getAmOrPmKeyCode(AM)) {
773b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                        deletedKeyStr = mAmText;
774b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                    } else if (deleted == getAmOrPmKeyCode(PM)) {
775b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                        deletedKeyStr = mPmText;
776b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                    } else {
777b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                        deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
778b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                    }
779b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                    mRadialTimePickerView.announceForAccessibility(
780b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                            String.format(mDeletedKeyFormat, deletedKeyStr));
781b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                    updateDisplay(true);
782b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                }
783b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            }
784b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
785b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
786b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
787b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
788b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
789b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                || (!mIs24HourView &&
790b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
791b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            if (!mInKbMode) {
792b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                if (mRadialTimePickerView == null) {
793b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                    // Something's wrong, because time picker should definitely not be null.
794b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                    Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null.");
7958aab17e82aab6b3f266ef1884253226d2cb8b8d8Fariborz Jahanian                    return true;
7968aab17e82aab6b3f266ef1884253226d2cb8b8d8Fariborz Jahanian                }
7977794cb85d394750db0631c02b7aa7837ae56764cFariborz Jahanian                mTypedTimes.clear();
798b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                tryStartingKbMode(keyCode);
799b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                return true;
800b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            }
801b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            // We're already in keyboard mode.
802b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            if (addKeyIfLegal(keyCode)) {
803b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                updateDisplay(false);
804b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            }
805b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            return true;
806b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        }
807b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        return false;
808b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian    }
809b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian
810b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian    /**
811b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian     * Try to start keyboard mode with the specified key.
812b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian     *
813b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian     * @param keyCode The key to use as the first press. Keyboard mode will not be started if the
814b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian     * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
815b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian     * key.
81689079eac7d6e3091b74b477a4a5ff0cdaf35496cFariborz Jahanian     */
81789079eac7d6e3091b74b477a4a5ff0cdaf35496cFariborz Jahanian    private void tryStartingKbMode(int keyCode) {
818b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        if (keyCode == -1 || addKeyIfLegal(keyCode)) {
819b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            mInKbMode = true;
820b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            onValidationChanged(false);
821b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            updateDisplay(false);
822b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            mRadialTimePickerView.setInputEnabled(false);
823bd49a647afd9cc534fef13cadf652d4e9c396e2bFariborz Jahanian        }
824bd49a647afd9cc534fef13cadf652d4e9c396e2bFariborz Jahanian    }
825ccdbc5c6f14965d91a352d114c109a9d4d9552b8Steve Naroff
826b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian    private boolean addKeyIfLegal(int keyCode) {
827b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
828b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        // we'll need to see if AM/PM have been typed.
829ccdbc5c6f14965d91a352d114c109a9d4d9552b8Steve Naroff        if ((mIs24HourView && mTypedTimes.size() == 4) ||
830b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                (!mIs24HourView && isTypedTimeFullyLegal())) {
831b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            return false;
8328aab17e82aab6b3f266ef1884253226d2cb8b8d8Fariborz Jahanian        }
8338aab17e82aab6b3f266ef1884253226d2cb8b8d8Fariborz Jahanian
8347573098b83e780d1c5bea13b384b610d8f155676Steve Naroff        mTypedTimes.add(keyCode);
8357573098b83e780d1c5bea13b384b610d8f155676Steve Naroff        if (!isTypedTimeLegalSoFar()) {
8367573098b83e780d1c5bea13b384b610d8f155676Steve Naroff            deleteLastTypedKey();
8377573098b83e780d1c5bea13b384b610d8f155676Steve Naroff            return false;
8387573098b83e780d1c5bea13b384b610d8f155676Steve Naroff        }
8397573098b83e780d1c5bea13b384b610d8f155676Steve Naroff
8407573098b83e780d1c5bea13b384b610d8f155676Steve Naroff        int val = getValFromKeyCode(keyCode);
8417573098b83e780d1c5bea13b384b610d8f155676Steve Naroff        mRadialTimePickerView.announceForAccessibility(String.format("%d", val));
8427573098b83e780d1c5bea13b384b610d8f155676Steve Naroff        // Automatically fill in 0's if AM or PM was legally entered.
8437573098b83e780d1c5bea13b384b610d8f155676Steve Naroff        if (isTypedTimeFullyLegal()) {
8447573098b83e780d1c5bea13b384b610d8f155676Steve Naroff            if (!mIs24HourView && mTypedTimes.size() <= 3) {
8457573098b83e780d1c5bea13b384b610d8f155676Steve Naroff                mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
846b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
847b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            }
848b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            onValidationChanged(true);
849b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        }
850b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian
851b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        return true;
852b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian    }
853b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian
854b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian    /**
855b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian     * Traverse the tree to see if the keys that have been typed so far are legal as is,
856b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian     * or may become legal as more keys are typed (excluding backspace).
857b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian     */
858b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian    private boolean isTypedTimeLegalSoFar() {
859fe795956194141c91ae555985c9b930595bff43fChris Lattner        Node node = mLegalTimesTree;
86039f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        for (int keyCode : mTypedTimes) {
86139f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            node = node.canReach(keyCode);
86239f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            if (node == null) {
86339f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian                return false;
86439f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            }
86539f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        }
86639f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        return true;
86739f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian    }
86839f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian
86939f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian    /**
87039f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian     * Check if the time that has been typed so far is completely legal, as is.
87139f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian     */
87239f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian    private boolean isTypedTimeFullyLegal() {
87339f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        if (mIs24HourView) {
87439f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
87539f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
87639f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            int[] values = getEnteredTime(null);
87739f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            return (values[0] >= 0 && values[1] >= 0 && values[1] < 60);
87839f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        } else {
87939f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be
88039f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            // legally added at specific times based on the tree's algorithm.
88139f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) ||
88239f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian                    mTypedTimes.contains(getAmOrPmKeyCode(PM)));
88339f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        }
88439f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian    }
88539f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian
88639f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian    private int deleteLastTypedKey() {
8875f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
8885f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        if (!isTypedTimeFullyLegal()) {
8895f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            onValidationChanged(false);
890        }
891        return deleted;
892    }
893
894    /**
895     * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.
896     */
897    private void finishKbMode() {
898        mInKbMode = false;
899        if (!mTypedTimes.isEmpty()) {
900            int values[] = getEnteredTime(null);
901            mRadialTimePickerView.setCurrentHour(values[0]);
902            mRadialTimePickerView.setCurrentMinute(values[1]);
903            if (!mIs24HourView) {
904                mRadialTimePickerView.setAmOrPm(values[2]);
905            }
906            mTypedTimes.clear();
907        }
908        updateDisplay(false);
909        mRadialTimePickerView.setInputEnabled(true);
910    }
911
912    /**
913     * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is
914     * empty, either show an empty display (filled with the placeholder text), or update from the
915     * timepicker's values.
916     *
917     * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text.
918     * Otherwise, revert to the timepicker's values.
919     */
920    private void updateDisplay(boolean allowEmptyDisplay) {
921        if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
922            int hour = mRadialTimePickerView.getCurrentHour();
923            int minute = mRadialTimePickerView.getCurrentMinute();
924            updateHeaderHour(hour, true);
925            updateHeaderMinute(minute);
926            if (!mIs24HourView) {
927                updateAmPmLabelStates(hour < 12 ? AM : PM);
928            }
929            setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true);
930            onValidationChanged(true);
931        } else {
932            boolean[] enteredZeros = {false, false};
933            int[] values = getEnteredTime(enteredZeros);
934            String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
935            String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
936            String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
937                    String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
938            String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
939                    String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
940            mHourView.setText(hourStr);
941            mHourView.setSelected(false);
942            mMinuteView.setText(minuteStr);
943            mMinuteView.setSelected(false);
944            if (!mIs24HourView) {
945                updateAmPmLabelStates(values[2]);
946            }
947        }
948    }
949
950    private int getValFromKeyCode(int keyCode) {
951        switch (keyCode) {
952            case KeyEvent.KEYCODE_0:
953                return 0;
954            case KeyEvent.KEYCODE_1:
955                return 1;
956            case KeyEvent.KEYCODE_2:
957                return 2;
958            case KeyEvent.KEYCODE_3:
959                return 3;
960            case KeyEvent.KEYCODE_4:
961                return 4;
962            case KeyEvent.KEYCODE_5:
963                return 5;
964            case KeyEvent.KEYCODE_6:
965                return 6;
966            case KeyEvent.KEYCODE_7:
967                return 7;
968            case KeyEvent.KEYCODE_8:
969                return 8;
970            case KeyEvent.KEYCODE_9:
971                return 9;
972            default:
973                return -1;
974        }
975    }
976
977    /**
978     * Get the currently-entered time, as integer values of the hours and minutes typed.
979     *
980     * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which
981     * may then be used for the caller to know whether zeros had been explicitly entered as either
982     * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
983     *
984     * @return A size-3 int array. The first value will be the hours, the second value will be the
985     * minutes, and the third will be either AM or PM.
986     */
987    private int[] getEnteredTime(boolean[] enteredZeros) {
988        int amOrPm = -1;
989        int startIndex = 1;
990        if (!mIs24HourView && isTypedTimeFullyLegal()) {
991            int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
992            if (keyCode == getAmOrPmKeyCode(AM)) {
993                amOrPm = AM;
994            } else if (keyCode == getAmOrPmKeyCode(PM)){
995                amOrPm = PM;
996            }
997            startIndex = 2;
998        }
999        int minute = -1;
1000        int hour = -1;
1001        for (int i = startIndex; i <= mTypedTimes.size(); i++) {
1002            int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
1003            if (i == startIndex) {
1004                minute = val;
1005            } else if (i == startIndex+1) {
1006                minute += 10 * val;
1007                if (enteredZeros != null && val == 0) {
1008                    enteredZeros[1] = true;
1009                }
1010            } else if (i == startIndex+2) {
1011                hour = val;
1012            } else if (i == startIndex+3) {
1013                hour += 10 * val;
1014                if (enteredZeros != null && val == 0) {
1015                    enteredZeros[0] = true;
1016                }
1017            }
1018        }
1019
1020        return new int[] { hour, minute, amOrPm };
1021    }
1022
1023    /**
1024     * Get the keycode value for AM and PM in the current language.
1025     */
1026    private int getAmOrPmKeyCode(int amOrPm) {
1027        // Cache the codes.
1028        if (mAmKeyCode == -1 || mPmKeyCode == -1) {
1029            // Find the first character in the AM/PM text that is unique.
1030            KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
1031            char amChar;
1032            char pmChar;
1033            for (int i = 0; i < Math.max(mAmText.length(), mPmText.length()); i++) {
1034                amChar = mAmText.toLowerCase(mCurrentLocale).charAt(i);
1035                pmChar = mPmText.toLowerCase(mCurrentLocale).charAt(i);
1036                if (amChar != pmChar) {
1037                    KeyEvent[] events = kcm.getEvents(new char[]{amChar, pmChar});
1038                    // There should be 4 events: a down and up for both AM and PM.
1039                    if (events != null && events.length == 4) {
1040                        mAmKeyCode = events[0].getKeyCode();
1041                        mPmKeyCode = events[2].getKeyCode();
1042                    } else {
1043                        Log.e(TAG, "Unable to find keycodes for AM and PM.");
1044                    }
1045                    break;
1046                }
1047            }
1048        }
1049        if (amOrPm == AM) {
1050            return mAmKeyCode;
1051        } else if (amOrPm == PM) {
1052            return mPmKeyCode;
1053        }
1054
1055        return -1;
1056    }
1057
1058    /**
1059     * Create a tree for deciding what keys can legally be typed.
1060     */
1061    private void generateLegalTimesTree() {
1062        // Create a quick cache of numbers to their keycodes.
1063        final int k0 = KeyEvent.KEYCODE_0;
1064        final int k1 = KeyEvent.KEYCODE_1;
1065        final int k2 = KeyEvent.KEYCODE_2;
1066        final int k3 = KeyEvent.KEYCODE_3;
1067        final int k4 = KeyEvent.KEYCODE_4;
1068        final int k5 = KeyEvent.KEYCODE_5;
1069        final int k6 = KeyEvent.KEYCODE_6;
1070        final int k7 = KeyEvent.KEYCODE_7;
1071        final int k8 = KeyEvent.KEYCODE_8;
1072        final int k9 = KeyEvent.KEYCODE_9;
1073
1074        // The root of the tree doesn't contain any numbers.
1075        mLegalTimesTree = new Node();
1076        if (mIs24HourView) {
1077            // We'll be re-using these nodes, so we'll save them.
1078            Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
1079            Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
1080            // The first digit must be followed by the second digit.
1081            minuteFirstDigit.addChild(minuteSecondDigit);
1082
1083            // The first digit may be 0-1.
1084            Node firstDigit = new Node(k0, k1);
1085            mLegalTimesTree.addChild(firstDigit);
1086
1087            // When the first digit is 0-1, the second digit may be 0-5.
1088            Node secondDigit = new Node(k0, k1, k2, k3, k4, k5);
1089            firstDigit.addChild(secondDigit);
1090            // We may now be followed by the first minute digit. E.g. 00:09, 15:58.
1091            secondDigit.addChild(minuteFirstDigit);
1092
1093            // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9.
1094            Node thirdDigit = new Node(k6, k7, k8, k9);
1095            // The time must now be finished. E.g. 0:55, 1:08.
1096            secondDigit.addChild(thirdDigit);
1097
1098            // When the first digit is 0-1, the second digit may be 6-9.
1099            secondDigit = new Node(k6, k7, k8, k9);
1100            firstDigit.addChild(secondDigit);
1101            // We must now be followed by the first minute digit. E.g. 06:50, 18:20.
1102            secondDigit.addChild(minuteFirstDigit);
1103
1104            // The first digit may be 2.
1105            firstDigit = new Node(k2);
1106            mLegalTimesTree.addChild(firstDigit);
1107
1108            // When the first digit is 2, the second digit may be 0-3.
1109            secondDigit = new Node(k0, k1, k2, k3);
1110            firstDigit.addChild(secondDigit);
1111            // We must now be followed by the first minute digit. E.g. 20:50, 23:09.
1112            secondDigit.addChild(minuteFirstDigit);
1113
1114            // When the first digit is 2, the second digit may be 4-5.
1115            secondDigit = new Node(k4, k5);
1116            firstDigit.addChild(secondDigit);
1117            // We must now be followd by the last minute digit. E.g. 2:40, 2:53.
1118            secondDigit.addChild(minuteSecondDigit);
1119
1120            // The first digit may be 3-9.
1121            firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9);
1122            mLegalTimesTree.addChild(firstDigit);
1123            // We must now be followed by the first minute digit. E.g. 3:57, 8:12.
1124            firstDigit.addChild(minuteFirstDigit);
1125        } else {
1126            // We'll need to use the AM/PM node a lot.
1127            // Set up AM and PM to respond to "a" and "p".
1128            Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));
1129
1130            // The first hour digit may be 1.
1131            Node firstDigit = new Node(k1);
1132            mLegalTimesTree.addChild(firstDigit);
1133            // We'll allow quick input of on-the-hour times. E.g. 1pm.
1134            firstDigit.addChild(ampm);
1135
1136            // When the first digit is 1, the second digit may be 0-2.
1137            Node secondDigit = new Node(k0, k1, k2);
1138            firstDigit.addChild(secondDigit);
1139            // Also for quick input of on-the-hour times. E.g. 10pm, 12am.
1140            secondDigit.addChild(ampm);
1141
1142            // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5.
1143            Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5);
1144            secondDigit.addChild(thirdDigit);
1145            // The time may be finished now. E.g. 1:02pm, 1:25am.
1146            thirdDigit.addChild(ampm);
1147
1148            // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
1149            // the fourth digit may be 0-9.
1150            Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
1151            thirdDigit.addChild(fourthDigit);
1152            // The time must be finished now. E.g. 10:49am, 12:40pm.
1153            fourthDigit.addChild(ampm);
1154
1155            // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9.
1156            thirdDigit = new Node(k6, k7, k8, k9);
1157            secondDigit.addChild(thirdDigit);
1158            // The time must be finished now. E.g. 1:08am, 1:26pm.
1159            thirdDigit.addChild(ampm);
1160
1161            // When the first digit is 1, the second digit may be 3-5.
1162            secondDigit = new Node(k3, k4, k5);
1163            firstDigit.addChild(secondDigit);
1164
1165            // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9.
1166            thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
1167            secondDigit.addChild(thirdDigit);
1168            // The time must be finished now. E.g. 1:39am, 1:50pm.
1169            thirdDigit.addChild(ampm);
1170
1171            // The hour digit may be 2-9.
1172            firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
1173            mLegalTimesTree.addChild(firstDigit);
1174            // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm.
1175            firstDigit.addChild(ampm);
1176
1177            // When the first digit is 2-9, the second digit may be 0-5.
1178            secondDigit = new Node(k0, k1, k2, k3, k4, k5);
1179            firstDigit.addChild(secondDigit);
1180
1181            // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9.
1182            thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
1183            secondDigit.addChild(thirdDigit);
1184            // The time must be finished now. E.g. 2:57am, 9:30pm.
1185            thirdDigit.addChild(ampm);
1186        }
1187    }
1188
1189    /**
1190     * Simple node class to be used for traversal to check for legal times.
1191     * mLegalKeys represents the keys that can be typed to get to the node.
1192     * mChildren are the children that can be reached from this node.
1193     */
1194    private class Node {
1195        private int[] mLegalKeys;
1196        private ArrayList<Node> mChildren;
1197
1198        public Node(int... legalKeys) {
1199            mLegalKeys = legalKeys;
1200            mChildren = new ArrayList<Node>();
1201        }
1202
1203        public void addChild(Node child) {
1204            mChildren.add(child);
1205        }
1206
1207        public boolean containsKey(int key) {
1208            for (int i = 0; i < mLegalKeys.length; i++) {
1209                if (mLegalKeys[i] == key) {
1210                    return true;
1211                }
1212            }
1213            return false;
1214        }
1215
1216        public Node canReach(int key) {
1217            if (mChildren == null) {
1218                return null;
1219            }
1220            for (Node child : mChildren) {
1221                if (child.containsKey(key)) {
1222                    return child;
1223                }
1224            }
1225            return null;
1226        }
1227    }
1228
1229    private final View.OnClickListener mClickListener = new View.OnClickListener() {
1230        @Override
1231        public void onClick(View v) {
1232
1233            final int amOrPm;
1234            switch (v.getId()) {
1235                case R.id.am_label:
1236                    setAmOrPm(AM);
1237                    break;
1238                case R.id.pm_label:
1239                    setAmOrPm(PM);
1240                    break;
1241                case R.id.hours:
1242                    setCurrentItemShowing(HOUR_INDEX, true, true);
1243                    break;
1244                case R.id.minutes:
1245                    setCurrentItemShowing(MINUTE_INDEX, true, true);
1246                    break;
1247                default:
1248                    // Failed to handle this click, don't vibrate.
1249                    return;
1250            }
1251
1252            tryVibrate();
1253        }
1254    };
1255
1256    private final View.OnKeyListener mKeyListener = new View.OnKeyListener() {
1257        @Override
1258        public boolean onKey(View v, int keyCode, KeyEvent event) {
1259            if (event.getAction() == KeyEvent.ACTION_UP) {
1260                return processKeyUp(keyCode);
1261            }
1262            return false;
1263        }
1264    };
1265
1266    private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() {
1267        @Override
1268        public void onFocusChange(View v, boolean hasFocus) {
1269            if (!hasFocus && mInKbMode && isTypedTimeFullyLegal()) {
1270                finishKbMode();
1271
1272                if (mOnTimeChangedListener != null) {
1273                    mOnTimeChangedListener.onTimeChanged(mDelegator,
1274                            mRadialTimePickerView.getCurrentHour(),
1275                            mRadialTimePickerView.getCurrentMinute());
1276                }
1277            }
1278        }
1279    };
1280}
1281