1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.deskclock;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.util.AttributeSet;
24import android.view.View;
25import android.widget.Button;
26import android.widget.TextView;
27
28import java.text.DateFormatSymbols;
29
30
31public class TimePicker extends TimerSetupView implements Button.OnClickListener{
32
33    private TextView mAmPmLabel;
34    private String[] mAmpm;
35    private final String mNoAmPmLabel;
36    private int mAmPmState;
37    private Button mSetButton;
38    private final boolean mIs24HoursMode = Alarms.get24HourMode(mContext);
39
40    private static final int AMPM_NOT_SELECTED = 0;
41    private static final int PM_SELECTED = 1;
42    private static final int AM_SELECTED = 2;
43    private static final int HOURS24_MODE = 3;
44
45    private static final String TIME_PICKER_SAVED_BUFFER_POINTER =
46            "timer_picker_saved_buffer_pointer";
47    private static final String TIME_PICKER_SAVED_INPUT = "timer_picker_saved_input";
48    private static final String TIME_PICKER_SAVED_AMPM = "timer_picker_saved_ampm";
49
50    public TimePicker(Context context) {
51        this(context, null);
52    }
53
54    public TimePicker(Context context, AttributeSet attrs) {
55        super(context, attrs);
56        mInputSize = 4;
57        mNoAmPmLabel = context.getResources().getString(R.string.time_picker_ampm_label);
58    }
59
60    @Override
61    protected int getLayoutId() {
62        return R.layout.time_picker_view;
63    }
64
65    @Override
66    protected void onFinishInflate() {
67        super.onFinishInflate();
68        Resources res = mContext.getResources();
69        mAmpm = new DateFormatSymbols().getAmPmStrings();
70
71        if (mIs24HoursMode) {
72            mLeft.setText(res.getString(R.string.time_picker_00_label));
73            mRight.setText(res.getString(R.string.time_picker_30_label));
74        } else {
75            mLeft.setText(mAmpm[0]);
76            mRight.setText(mAmpm[1]);
77        }
78        mLeft.setOnClickListener(this);
79        mRight.setOnClickListener(this);
80        mAmPmLabel = (TextView)findViewById(R.id.ampm_label);
81        mAmPmState = AMPM_NOT_SELECTED;
82        updateKeypad();
83    }
84
85
86    @Override
87    public void onClick(View v) {
88        doOnClick(v);
89    }
90
91    @Override
92    protected void doOnClick(View v) {
93        Integer val = (Integer) v.getTag(R.id.numbers_key);
94        // A number was pressed
95        if (val != null) {
96            addClickedNumber(val);
97        } else if (v == mDelete) {
98            // Pressing delete when AM or PM is selected, clears the AM/PM
99            // selection
100            if (!mIs24HoursMode && mAmPmState != AMPM_NOT_SELECTED) {
101                mAmPmState = AMPM_NOT_SELECTED;
102            } else if (mInputPointer >= 0) {
103                for (int i = 0; i < mInputPointer; i++) {
104                    mInput[i] = mInput[i + 1];
105                }
106                mInput[mInputPointer] = 0;
107                mInputPointer--;
108            }
109        } else if (v == mLeft) {
110            onLeftClicked();
111        } else if (v == mRight) {
112            onRightClicked();
113        }
114        updateKeypad();
115    }
116
117    @Override
118    public boolean onLongClick(View v) {
119        if (v == mDelete) {
120            mAmPmState = AMPM_NOT_SELECTED;
121            reset();
122            updateKeypad();
123            return true;
124        }
125        return false;
126    }
127
128    private void updateKeypad() {
129        // Update state of keypad
130        // Set the correct AM/PM state
131        showAmPm();
132        // Update the time
133        updateLeftRightButtons();
134        updateTime();
135        // enable/disable numeric keys according to the numbers entered already
136        updateNumericKeys();
137        // enable/disable the "set" key
138        enableSetButton();
139        // Update the backspace button
140        updateDeleteButton();
141
142    }
143
144    // Update the time displayed in the picker:
145    // Special cases:
146    // 1. show "-" for digits not entered yet.
147    // 2. hide the hours digits when it is not relevant
148    @Override
149    protected void updateTime() {
150        // Put "-" in digits that was not entered by passing -1
151        // Hide digit by passing -2 (for highest hours digit only);
152
153        int  hours1 = -1;
154        int time = getEnteredTime();
155        // If the user entered 2 to 9 or 13 to 15 , there is no need for a 4th digit (AM/PM mode)
156        // If the user entered 3 to 9 or 24 to 25 , there is no need for a 4th digit (24 hours mode)
157        if (mInputPointer > -1) {
158            // Test to see if the highest digit is 2 to 9 for AM/PM or 3 to 9 for 24 hours mode
159            if (mInputPointer >= 0) {
160                int digit = mInput[mInputPointer];
161                if ((mIs24HoursMode && digit >= 3 && digit <= 9) ||
162                        (!mIs24HoursMode && digit >= 2 && digit <= 9)) {
163                    hours1 = -2;
164                }
165            }
166            // Test to see if the 2 highest digits are 13 to 15 for AM/PM or 24 to 25 for 24 hours
167            // mode
168            if (mInputPointer > 0 && mInputPointer < 3 && hours1 != -2) {
169                int digits = mInput[mInputPointer] * 10 + mInput[mInputPointer - 1];
170                if ((mIs24HoursMode && digits >= 24 && digits <= 25) ||
171                        (!mIs24HoursMode && digits >= 13 && digits <= 15)) {
172                    hours1 = -2;
173                }
174            }
175            // If we have a digit show it
176            if (mInputPointer == 3) {
177                hours1 = mInput[3];
178            }
179        } else {
180            hours1 = -1;
181        }
182        int hours2 = (mInputPointer < 2) ? -1 :  mInput[2];
183        int minutes1 = (mInputPointer < 1) ? -1 :  mInput[1];
184        int minutes2 = (mInputPointer < 0) ? -1 :  mInput[0];
185        mEnteredTime.setTime(hours1, hours2, minutes1, minutes2, -1);
186    }
187
188    private void showAmPm() {
189        if (!mIs24HoursMode) {
190            switch(mAmPmState) {
191                case AMPM_NOT_SELECTED:
192                    mAmPmLabel.setText(mNoAmPmLabel);
193                    break;
194                case AM_SELECTED:
195                    mAmPmLabel.setText(mAmpm[0]);
196                    break;
197                case PM_SELECTED:
198                    mAmPmLabel.setText(mAmpm[1]);
199                    break;
200                default:
201                    break;
202            }
203        } else {
204            mAmPmLabel.setVisibility(View.INVISIBLE);
205            mAmPmState = HOURS24_MODE;
206        }
207    }
208
209    private void addClickedNumber(int val) {
210        if (mInputPointer < mInputSize - 1) {
211            for (int i = mInputPointer; i >= 0; i--) {
212                mInput[i + 1] = mInput[i];
213            }
214            mInputPointer++;
215            mInput[0] = val;
216        }
217    }
218
219    // Clicking on the bottom left button will add "00" to the time
220    // In AM/PM mode is will also set the time to AM.
221    private void onLeftClicked() {
222        int time = getEnteredTime();
223        if (!mIs24HoursMode) {
224            if (canAddDigits()) {
225                addClickedNumber(0);
226                addClickedNumber(0);
227            }
228            mAmPmState = AM_SELECTED;
229        } else if (canAddDigits()) {
230                addClickedNumber(0);
231                addClickedNumber(0);
232        }
233    }
234
235    // Clicking on the bottom right button will add "00" to the time in AM/PM
236    // mode and "30" is 24 hours mode.
237    // In AM/PM mode is will also set the time to PM.
238    private void onRightClicked() {
239        int time = getEnteredTime();
240        if (!mIs24HoursMode) {
241            if (canAddDigits()) {
242                addClickedNumber(0);
243                addClickedNumber(0);
244            }
245            mAmPmState = PM_SELECTED;
246        } else {
247            if (canAddDigits()) {
248                addClickedNumber(3);
249                addClickedNumber(0);
250            }
251        }
252    }
253
254    // Checks if the user allowed to click on the left or right button that enters "00" or "30"
255    private boolean canAddDigits() {
256        int time = getEnteredTime();
257        // For AM/PM mode , can add "00" if an hour between 1 and 12 was entered
258        if (!mIs24HoursMode) {
259            return (time >= 1 && time <= 12);
260        }
261        // For 24 hours mode , can add "00"/"30" if an hour between 0 and 23 was entered
262        return (time >= 0 && time <= 23 && mInputPointer > -1 && mInputPointer < 2);
263    }
264
265
266    // Enable/disable keys in the numeric key pad according to the data entered
267    private void updateNumericKeys() {
268        int time = getEnteredTime();
269        if (mIs24HoursMode) {
270            if (mInputPointer >= 3) {
271                setKeyRange(-1);
272            } else if (time == 0) {
273                if (mInputPointer == -1 || mInputPointer == 0 || mInputPointer == 2) {
274                    setKeyRange(9);
275                } else if (mInputPointer == 1) {
276                    setKeyRange(5);
277                } else {
278                    setKeyRange(-1);
279                }
280            } else if (time == 1) {
281                if (mInputPointer == 0 || mInputPointer == 2) {
282                    setKeyRange(9);
283                } else  if (mInputPointer == 1) {
284                    setKeyRange(5);
285                } else {
286                    setKeyRange(-1);
287                }
288            } else if (time == 2) {
289                if (mInputPointer == 2|| mInputPointer == 1) {
290                    setKeyRange(9);
291                } else  if (mInputPointer == 0) {
292                    setKeyRange(3);
293                } else {
294                    setKeyRange(-1);
295                }
296            } else if (time <= 5) {
297                setKeyRange(9);
298            } else if (time <= 9) {
299                setKeyRange(5);
300            } else if (time >= 10 && time <= 15) {
301                setKeyRange(9);
302            } else if (time >= 16 && time <= 19) {
303                setKeyRange(5);
304            } else if (time >= 20 && time <= 25) {
305                setKeyRange(9);
306            } else if (time >= 26 && time <= 29) {
307                setKeyRange(-1);
308            } else if (time >= 30 && time <= 35) {
309                setKeyRange(9);
310            } else if (time >= 36 && time <= 39) {
311                setKeyRange(-1);
312            } else if (time >= 40 && time <= 45) {
313                setKeyRange(9);
314            } else if (time >= 46 && time <= 49) {
315                setKeyRange(-1);
316            } else if (time >= 50 && time <= 55) {
317                setKeyRange(9);
318            } else if (time >= 56 && time <= 59) {
319                setKeyRange(-1);
320            } else if (time >= 60 && time <= 65) {
321                setKeyRange(9);
322            } else if (time >= 70 && time <= 75) {
323                setKeyRange(9);
324            } else if (time >= 80 && time <= 85) {
325                setKeyRange(9);
326            } else if (time >= 90 && time <= 95) {
327                setKeyRange(9);
328            } else if (time >= 100 && time <= 105) {
329                setKeyRange(9);
330            } else if (time >= 106 && time <= 109) {
331                setKeyRange(-1);
332            } else if (time >= 110 && time <= 115) {
333                setKeyRange(9);
334            } else if (time >= 116 && time <= 119) {
335                setKeyRange(-1);
336            } else if (time >= 120 && time <= 125) {
337                setKeyRange(9);
338            } else if (time >= 126 && time <= 129) {
339                setKeyRange(-1);
340            } else if (time >= 130 && time <= 135) {
341                setKeyRange(9);
342            } else if (time >= 136 && time <= 139) {
343                setKeyRange(-1);
344            } else if (time >= 140 && time <= 145) {
345                setKeyRange(9);
346            } else if (time >= 146 && time <= 149) {
347                setKeyRange(-1);
348            } else if (time >= 150 && time <= 155) {
349                setKeyRange(9);
350            } else if (time >= 156 && time <= 159) {
351                setKeyRange(-1);
352            } else if (time >= 160 && time <= 165) {
353                setKeyRange(9);
354            } else if (time >= 166 && time <= 169) {
355                setKeyRange(-1);
356            } else if (time >= 170 && time <= 175) {
357                setKeyRange(9);
358            } else if (time >= 176 && time <= 179) {
359                setKeyRange(-1);
360            } else if (time >= 180 && time <= 185) {
361                setKeyRange(9);
362            } else if (time >= 186 && time <= 189) {
363                setKeyRange(-1);
364            } else if (time >= 190 && time <= 195) {
365                setKeyRange(9);
366            } else if (time >= 196 && time <= 199) {
367                setKeyRange(-1);
368            } else if (time >= 200 && time <= 205) {
369                setKeyRange(9);
370            } else if (time >= 206 && time <= 209) {
371                setKeyRange(-1);
372            } else if (time >= 210 && time <= 215) {
373                setKeyRange(9);
374            } else if (time >= 216 && time <= 219) {
375                setKeyRange(-1);
376            } else if (time >= 220 && time <= 225) {
377                setKeyRange(9);
378            } else if (time >= 226 && time <= 229) {
379                setKeyRange(-1);
380            } else if (time >= 230 && time <= 235) {
381                setKeyRange(9);
382            } else if (time >= 236) {
383                setKeyRange(-1);
384            }
385        } else {
386            // Selecting AM/PM disabled the keypad
387            if (mAmPmState != AMPM_NOT_SELECTED) {
388                setKeyRange(-1);
389            } else if (time == 0) {
390                setKeyRange(9);
391                // If 0 was entered as the first digit in AM/PM mode, do not allow a second 0
392        //        if (mInputPointer == 0) {
393                    mNumbers[0].setEnabled(false);
394          //      }
395            } else if (time <= 9) {
396                setKeyRange(5);
397            } else if (time <= 95) {
398                setKeyRange(9);
399            } else if (time >= 100 && time <= 105) {
400                setKeyRange(9);
401            } else if (time >= 106 && time <= 109) {
402                setKeyRange(-1);
403            } else if (time >= 110 && time <= 115) {
404                setKeyRange(9);
405            } else if (time >= 116 && time <= 119) {
406                setKeyRange(-1);
407            } else if (time >= 120 && time <= 125) {
408                setKeyRange(9);
409            } else if (time >= 126) {
410                setKeyRange(-1);
411            }
412        }
413    }
414
415    // Returns the time already entered in decimal representation. if time is H1 H2 : M1 M2
416    // the value retured is H1*1000+H2*100+M1*10+M2
417    private int getEnteredTime() {
418        return  mInput[3] * 1000 + mInput[2] * 100 + mInput[1] * 10 + mInput[0];
419    }
420
421    // enables a range of numeric keys from zero to maxKey. The rest of the keys will be disabled
422    private void setKeyRange(int maxKey) {
423        for (int i = 0; i < mNumbers.length; i++) {
424            mNumbers[i].setEnabled(i <= maxKey);
425        }
426    }
427
428    private void updateLeftRightButtons() {
429        int time = getEnteredTime();
430        if (mIs24HoursMode) {
431            boolean enable = canAddDigits();
432            mLeft.setEnabled(enable);
433            mRight.setEnabled(enable);
434        } else {
435            // You can use the AM/PM if time entered is 0 to 12 or it is 3 digits or more
436            if ((time > 12 && time < 100) || time == 0 || mAmPmState != AMPM_NOT_SELECTED) {
437                mLeft.setEnabled(false);
438                mRight.setEnabled(false);
439            } else {
440                mLeft.setEnabled(true);
441                mRight.setEnabled(true);
442            }
443        }
444    }
445
446    // Enable/disable the set key
447    private void enableSetButton() {
448        if (mSetButton == null) {
449            return;
450        }
451
452        // Nothing entered - disable
453        if (mInputPointer == -1) {
454            mSetButton.setEnabled(false);
455            return;
456        }
457        // If the user entered 3 digits or more but not 060 to 095
458        // it is a legal time and the set key should be enabled.
459        if (mIs24HoursMode) {
460            int time = getEnteredTime();
461            mSetButton.setEnabled(mInputPointer >= 2 && (time < 60 || time > 95));
462        } else {
463            // If AM/PM mode , enable the set button if AM/PM was selected
464            mSetButton.setEnabled(mAmPmState != AMPM_NOT_SELECTED);
465        }
466    }
467
468    public void setSetButton(Button b) {
469        mSetButton = b;
470        enableSetButton();
471    }
472
473    public int getHours() {
474        int hours = mInput[3] * 10 + mInput[2];
475        if (hours == 12) {
476            switch (mAmPmState) {
477                case PM_SELECTED:
478                    return 12;
479                case AM_SELECTED:
480                    return 0;
481                case HOURS24_MODE:
482                    return hours;
483                default:
484                    break;
485            }
486        }
487        return hours + (mAmPmState == PM_SELECTED ? 12 : 0);
488    }
489    public int getMinutes() {
490        return mInput[1] * 10 + mInput[0];
491    }
492
493    @Override
494    public Parcelable onSaveInstanceState () {
495        final Parcelable parcel = super.onSaveInstanceState();
496        final SavedState state = new SavedState(parcel);
497        state.mInput = mInput;
498        state.mAmPmState = mAmPmState;
499        state.mInputPointer = mInputPointer;
500        return state;
501    }
502
503    @Override
504    protected void onRestoreInstanceState (Parcelable state) {
505        if (!(state instanceof SavedState)) {
506            super.onRestoreInstanceState(state);
507            return;
508        }
509
510        final SavedState savedState = (SavedState) state;
511        super.onRestoreInstanceState(savedState.getSuperState());
512
513        mInputPointer = savedState.mInputPointer;
514        mInput = savedState.mInput;
515        if (mInput == null) {
516            mInput = new int[mInputSize];
517            mInputPointer = -1;
518        }
519        mAmPmState = savedState.mAmPmState;
520        updateKeypad();
521    }
522
523    private static class SavedState extends BaseSavedState {
524        int mInputPointer;
525        int[] mInput;
526        int mAmPmState;
527
528        public SavedState(Parcelable superState) {
529            super(superState);
530        }
531
532        private SavedState(Parcel in) {
533            super(in);
534            mInputPointer = in.readInt();
535            in.readIntArray(mInput);
536            mAmPmState = in.readInt();
537        }
538
539        @Override
540        public void writeToParcel(Parcel dest, int flags) {
541            super.writeToParcel(dest, flags);
542            dest.writeInt(mInputPointer);
543            dest.writeIntArray(mInput);
544            dest.writeInt(mAmPmState);
545        }
546
547        public static final Parcelable.Creator<SavedState> CREATOR
548                = new Parcelable.Creator<SavedState>() {
549            public SavedState createFromParcel(Parcel in) {
550                return new SavedState(in);
551            }
552
553            public SavedState[] newArray(int size) {
554                return new SavedState[size];
555            }
556        };
557    }
558}
559