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