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 17 package com.android.deskclock; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.util.AttributeSet; 24 import android.view.View; 25 import android.widget.Button; 26 import android.widget.TextView; 27 28 import java.text.DateFormatSymbols; 29 30 31 public 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