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