TextClock.java revision 94a6d15ede149189bba9e5f474ed853c98230e75
1/* 2 * Copyright (C) 2012 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 android.widget; 18 19import android.annotation.NonNull; 20import android.app.ActivityManager; 21import android.content.BroadcastReceiver; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.content.res.TypedArray; 27import android.database.ContentObserver; 28import android.net.Uri; 29import android.os.Handler; 30import android.os.SystemClock; 31import android.os.UserHandle; 32import android.provider.Settings; 33import android.text.format.DateFormat; 34import android.util.AttributeSet; 35import android.view.RemotableViewMethod; 36import android.view.ViewHierarchyEncoder; 37 38import com.android.internal.R; 39 40import java.util.Calendar; 41import java.util.TimeZone; 42 43import libcore.icu.LocaleData; 44 45import static android.view.ViewDebug.ExportedProperty; 46import static android.widget.RemoteViews.*; 47 48/** 49 * <p><code>TextClock</code> can display the current date and/or time as 50 * a formatted string.</p> 51 * 52 * <p>This view honors the 24-hour format system setting. As such, it is 53 * possible and recommended to provide two different formatting patterns: 54 * one to display the date/time in 24-hour mode and one to display the 55 * date/time in 12-hour mode. Most callers will want to use the defaults, 56 * though, which will be appropriate for the user's locale.</p> 57 * 58 * <p>It is possible to determine whether the system is currently in 59 * 24-hour mode by calling {@link #is24HourModeEnabled()}.</p> 60 * 61 * <p>The rules used by this widget to decide how to format the date and 62 * time are the following:</p> 63 * <ul> 64 * <li>In 24-hour mode: 65 * <ul> 66 * <li>Use the value returned by {@link #getFormat24Hour()} when non-null</li> 67 * <li>Otherwise, use the value returned by {@link #getFormat12Hour()} when non-null</li> 68 * <li>Otherwise, use a default value appropriate for the user's locale, such as {@code h:mm a}</li> 69 * </ul> 70 * </li> 71 * <li>In 12-hour mode: 72 * <ul> 73 * <li>Use the value returned by {@link #getFormat12Hour()} when non-null</li> 74 * <li>Otherwise, use the value returned by {@link #getFormat24Hour()} when non-null</li> 75 * <li>Otherwise, use a default value appropriate for the user's locale, such as {@code HH:mm}</li> 76 * </ul> 77 * </li> 78 * </ul> 79 * 80 * <p>The {@link CharSequence} instances used as formatting patterns when calling either 81 * {@link #setFormat24Hour(CharSequence)} or {@link #setFormat12Hour(CharSequence)} can 82 * contain styling information. To do so, use a {@link android.text.Spanned} object. 83 * Note that if you customize these strings, it is your responsibility to supply strings 84 * appropriate for formatting dates and/or times in the user's locale.</p> 85 * 86 * @attr ref android.R.styleable#TextClock_format12Hour 87 * @attr ref android.R.styleable#TextClock_format24Hour 88 * @attr ref android.R.styleable#TextClock_timeZone 89 */ 90@RemoteView 91public class TextClock extends TextView { 92 /** 93 * The default formatting pattern in 12-hour mode. This pattern is used 94 * if {@link #setFormat12Hour(CharSequence)} is called with a null pattern 95 * or if no pattern was specified when creating an instance of this class. 96 * 97 * This default pattern shows only the time, hours and minutes, and an am/pm 98 * indicator. 99 * 100 * @see #setFormat12Hour(CharSequence) 101 * @see #getFormat12Hour() 102 * 103 * @deprecated Let the system use locale-appropriate defaults instead. 104 */ 105 public static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a"; 106 107 /** 108 * The default formatting pattern in 24-hour mode. This pattern is used 109 * if {@link #setFormat24Hour(CharSequence)} is called with a null pattern 110 * or if no pattern was specified when creating an instance of this class. 111 * 112 * This default pattern shows only the time, hours and minutes. 113 * 114 * @see #setFormat24Hour(CharSequence) 115 * @see #getFormat24Hour() 116 * 117 * @deprecated Let the system use locale-appropriate defaults instead. 118 */ 119 public static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm"; 120 121 private CharSequence mFormat12; 122 private CharSequence mFormat24; 123 124 @ExportedProperty 125 private CharSequence mFormat; 126 @ExportedProperty 127 private boolean mHasSeconds; 128 129 private boolean mAttached; 130 131 private Calendar mTime; 132 private String mTimeZone; 133 134 private boolean mShowCurrentUserTime; 135 136 private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { 137 @Override 138 public void onChange(boolean selfChange) { 139 chooseFormat(); 140 onTimeChanged(); 141 } 142 143 @Override 144 public void onChange(boolean selfChange, Uri uri) { 145 chooseFormat(); 146 onTimeChanged(); 147 } 148 }; 149 150 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 151 @Override 152 public void onReceive(Context context, Intent intent) { 153 if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { 154 final String timeZone = intent.getStringExtra("time-zone"); 155 createTime(timeZone); 156 } 157 onTimeChanged(); 158 } 159 }; 160 161 private final Runnable mTicker = new Runnable() { 162 public void run() { 163 onTimeChanged(); 164 165 long now = SystemClock.uptimeMillis(); 166 long next = now + (1000 - now % 1000); 167 168 getHandler().postAtTime(mTicker, next); 169 } 170 }; 171 172 /** 173 * Creates a new clock using the default patterns for the current locale. 174 * 175 * @param context The Context the view is running in, through which it can 176 * access the current theme, resources, etc. 177 */ 178 @SuppressWarnings("UnusedDeclaration") 179 public TextClock(Context context) { 180 super(context); 181 init(); 182 } 183 184 /** 185 * Creates a new clock inflated from XML. This object's properties are 186 * intialized from the attributes specified in XML. 187 * 188 * This constructor uses a default style of 0, so the only attribute values 189 * applied are those in the Context's Theme and the given AttributeSet. 190 * 191 * @param context The Context the view is running in, through which it can 192 * access the current theme, resources, etc. 193 * @param attrs The attributes of the XML tag that is inflating the view 194 */ 195 @SuppressWarnings("UnusedDeclaration") 196 public TextClock(Context context, AttributeSet attrs) { 197 this(context, attrs, 0); 198 } 199 200 /** 201 * Creates a new clock inflated from XML. This object's properties are 202 * intialized from the attributes specified in XML. 203 * 204 * @param context The Context the view is running in, through which it can 205 * access the current theme, resources, etc. 206 * @param attrs The attributes of the XML tag that is inflating the view 207 * @param defStyleAttr An attribute in the current theme that contains a 208 * reference to a style resource that supplies default values for 209 * the view. Can be 0 to not look for defaults. 210 */ 211 public TextClock(Context context, AttributeSet attrs, int defStyleAttr) { 212 this(context, attrs, defStyleAttr, 0); 213 } 214 215 public TextClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 216 super(context, attrs, defStyleAttr, defStyleRes); 217 218 final TypedArray a = context.obtainStyledAttributes( 219 attrs, R.styleable.TextClock, defStyleAttr, defStyleRes); 220 try { 221 mFormat12 = a.getText(R.styleable.TextClock_format12Hour); 222 mFormat24 = a.getText(R.styleable.TextClock_format24Hour); 223 mTimeZone = a.getString(R.styleable.TextClock_timeZone); 224 } finally { 225 a.recycle(); 226 } 227 228 init(); 229 } 230 231 private void init() { 232 if (mFormat12 == null || mFormat24 == null) { 233 LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale); 234 if (mFormat12 == null) { 235 mFormat12 = ld.timeFormat_hm; 236 } 237 if (mFormat24 == null) { 238 mFormat24 = ld.timeFormat_Hm; 239 } 240 } 241 242 createTime(mTimeZone); 243 // Wait until onAttachedToWindow() to handle the ticker 244 chooseFormat(false); 245 } 246 247 private void createTime(String timeZone) { 248 if (timeZone != null) { 249 mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); 250 } else { 251 mTime = Calendar.getInstance(); 252 } 253 } 254 255 /** 256 * Returns the formatting pattern used to display the date and/or time 257 * in 12-hour mode. The formatting pattern syntax is described in 258 * {@link DateFormat}. 259 * 260 * @return A {@link CharSequence} or null. 261 * 262 * @see #setFormat12Hour(CharSequence) 263 * @see #is24HourModeEnabled() 264 */ 265 @ExportedProperty 266 public CharSequence getFormat12Hour() { 267 return mFormat12; 268 } 269 270 /** 271 * <p>Specifies the formatting pattern used to display the date and/or time 272 * in 12-hour mode. The formatting pattern syntax is described in 273 * {@link DateFormat}.</p> 274 * 275 * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used 276 * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns 277 * are set to null, the default pattern for the current locale will be used 278 * instead.</p> 279 * 280 * <p><strong>Note:</strong> if styling is not needed, it is highly recommended 281 * you supply a format string generated by 282 * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method 283 * takes care of generating a format string adapted to the desired locale.</p> 284 * 285 * 286 * @param format A date/time formatting pattern as described in {@link DateFormat} 287 * 288 * @see #getFormat12Hour() 289 * @see #is24HourModeEnabled() 290 * @see DateFormat#getBestDateTimePattern(java.util.Locale, String) 291 * @see DateFormat 292 * 293 * @attr ref android.R.styleable#TextClock_format12Hour 294 */ 295 @RemotableViewMethod 296 public void setFormat12Hour(CharSequence format) { 297 mFormat12 = format; 298 299 chooseFormat(); 300 onTimeChanged(); 301 } 302 303 /** 304 * Returns the formatting pattern used to display the date and/or time 305 * in 24-hour mode. The formatting pattern syntax is described in 306 * {@link DateFormat}. 307 * 308 * @return A {@link CharSequence} or null. 309 * 310 * @see #setFormat24Hour(CharSequence) 311 * @see #is24HourModeEnabled() 312 */ 313 @ExportedProperty 314 public CharSequence getFormat24Hour() { 315 return mFormat24; 316 } 317 318 /** 319 * <p>Specifies the formatting pattern used to display the date and/or time 320 * in 24-hour mode. The formatting pattern syntax is described in 321 * {@link DateFormat}.</p> 322 * 323 * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used 324 * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns 325 * are set to null, the default pattern for the current locale will be used 326 * instead.</p> 327 * 328 * <p><strong>Note:</strong> if styling is not needed, it is highly recommended 329 * you supply a format string generated by 330 * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method 331 * takes care of generating a format string adapted to the desired locale.</p> 332 * 333 * @param format A date/time formatting pattern as described in {@link DateFormat} 334 * 335 * @see #getFormat24Hour() 336 * @see #is24HourModeEnabled() 337 * @see DateFormat#getBestDateTimePattern(java.util.Locale, String) 338 * @see DateFormat 339 * 340 * @attr ref android.R.styleable#TextClock_format24Hour 341 */ 342 @RemotableViewMethod 343 public void setFormat24Hour(CharSequence format) { 344 mFormat24 = format; 345 346 chooseFormat(); 347 onTimeChanged(); 348 } 349 350 /** 351 * Sets whether this clock should always track the current user and not the user of the 352 * current process. This is used for single instance processes like the systemUI who need 353 * to display time for different users. 354 * 355 * @hide 356 */ 357 public void setShowCurrentUserTime(boolean showCurrentUserTime) { 358 mShowCurrentUserTime = showCurrentUserTime; 359 360 chooseFormat(); 361 onTimeChanged(); 362 unregisterObserver(); 363 registerObserver(); 364 } 365 366 /** 367 * Indicates whether the system is currently using the 24-hour mode. 368 * 369 * When the system is in 24-hour mode, this view will use the pattern 370 * returned by {@link #getFormat24Hour()}. In 12-hour mode, the pattern 371 * returned by {@link #getFormat12Hour()} is used instead. 372 * 373 * If either one of the formats is null, the other format is used. If 374 * both formats are null, the default formats for the current locale are used. 375 * 376 * @return true if time should be displayed in 24-hour format, false if it 377 * should be displayed in 12-hour format. 378 * 379 * @see #setFormat12Hour(CharSequence) 380 * @see #getFormat12Hour() 381 * @see #setFormat24Hour(CharSequence) 382 * @see #getFormat24Hour() 383 */ 384 public boolean is24HourModeEnabled() { 385 if (mShowCurrentUserTime) { 386 return DateFormat.is24HourFormat(getContext(), ActivityManager.getCurrentUser()); 387 } else { 388 return DateFormat.is24HourFormat(getContext()); 389 } 390 } 391 392 /** 393 * Indicates which time zone is currently used by this view. 394 * 395 * @return The ID of the current time zone or null if the default time zone, 396 * as set by the user, must be used 397 * 398 * @see TimeZone 399 * @see java.util.TimeZone#getAvailableIDs() 400 * @see #setTimeZone(String) 401 */ 402 public String getTimeZone() { 403 return mTimeZone; 404 } 405 406 /** 407 * Sets the specified time zone to use in this clock. When the time zone 408 * is set through this method, system time zone changes (when the user 409 * sets the time zone in settings for instance) will be ignored. 410 * 411 * @param timeZone The desired time zone's ID as specified in {@link TimeZone} 412 * or null to user the time zone specified by the user 413 * (system time zone) 414 * 415 * @see #getTimeZone() 416 * @see java.util.TimeZone#getAvailableIDs() 417 * @see TimeZone#getTimeZone(String) 418 * 419 * @attr ref android.R.styleable#TextClock_timeZone 420 */ 421 @RemotableViewMethod 422 public void setTimeZone(String timeZone) { 423 mTimeZone = timeZone; 424 425 createTime(timeZone); 426 onTimeChanged(); 427 } 428 429 /** 430 * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} 431 * depending on whether the user has selected 24-hour format. 432 * 433 * Calling this method does not schedule or unschedule the time ticker. 434 */ 435 private void chooseFormat() { 436 chooseFormat(true); 437 } 438 439 /** 440 * Returns the current format string. Always valid after constructor has 441 * finished, and will never be {@code null}. 442 * 443 * @hide 444 */ 445 public CharSequence getFormat() { 446 return mFormat; 447 } 448 449 /** 450 * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} 451 * depending on whether the user has selected 24-hour format. 452 * 453 * @param handleTicker true if calling this method should schedule/unschedule the 454 * time ticker, false otherwise 455 */ 456 private void chooseFormat(boolean handleTicker) { 457 final boolean format24Requested = is24HourModeEnabled(); 458 459 LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale); 460 461 if (format24Requested) { 462 mFormat = abc(mFormat24, mFormat12, ld.timeFormat_Hm); 463 } else { 464 mFormat = abc(mFormat12, mFormat24, ld.timeFormat_hm); 465 } 466 467 boolean hadSeconds = mHasSeconds; 468 mHasSeconds = DateFormat.hasSeconds(mFormat); 469 470 if (handleTicker && mAttached && hadSeconds != mHasSeconds) { 471 if (hadSeconds) getHandler().removeCallbacks(mTicker); 472 else mTicker.run(); 473 } 474 } 475 476 /** 477 * Returns a if not null, else return b if not null, else return c. 478 */ 479 private static CharSequence abc(CharSequence a, CharSequence b, CharSequence c) { 480 return a == null ? (b == null ? c : b) : a; 481 } 482 483 @Override 484 protected void onAttachedToWindow() { 485 super.onAttachedToWindow(); 486 487 if (!mAttached) { 488 mAttached = true; 489 490 registerReceiver(); 491 registerObserver(); 492 493 createTime(mTimeZone); 494 495 if (mHasSeconds) { 496 mTicker.run(); 497 } else { 498 onTimeChanged(); 499 } 500 } 501 } 502 503 @Override 504 protected void onDetachedFromWindow() { 505 super.onDetachedFromWindow(); 506 507 if (mAttached) { 508 unregisterReceiver(); 509 unregisterObserver(); 510 511 getHandler().removeCallbacks(mTicker); 512 513 mAttached = false; 514 } 515 } 516 517 private void registerReceiver() { 518 final IntentFilter filter = new IntentFilter(); 519 520 filter.addAction(Intent.ACTION_TIME_TICK); 521 filter.addAction(Intent.ACTION_TIME_CHANGED); 522 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 523 524 getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); 525 } 526 527 private void registerObserver() { 528 final ContentResolver resolver = getContext().getContentResolver(); 529 if (mShowCurrentUserTime) { 530 resolver.registerContentObserver(Settings.System.CONTENT_URI, true, 531 mFormatChangeObserver, UserHandle.USER_ALL); 532 } else { 533 resolver.registerContentObserver(Settings.System.CONTENT_URI, true, 534 mFormatChangeObserver); 535 } 536 } 537 538 private void unregisterReceiver() { 539 getContext().unregisterReceiver(mIntentReceiver); 540 } 541 542 private void unregisterObserver() { 543 final ContentResolver resolver = getContext().getContentResolver(); 544 resolver.unregisterContentObserver(mFormatChangeObserver); 545 } 546 547 private void onTimeChanged() { 548 mTime.setTimeInMillis(System.currentTimeMillis()); 549 setText(DateFormat.format(mFormat, mTime)); 550 } 551 552 /** @hide */ 553 @Override 554 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 555 super.encodeProperties(stream); 556 557 CharSequence s = getFormat12Hour(); 558 stream.addProperty("format12Hour", s == null ? null : s.toString()); 559 560 s = getFormat24Hour(); 561 stream.addProperty("format24Hour", s == null ? null : s.toString()); 562 stream.addProperty("format", mFormat == null ? null : mFormat.toString()); 563 stream.addProperty("hasSeconds", mHasSeconds); 564 } 565} 566