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