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 * @deprecated Let the system use locale-appropriate defaults instead. 99 */ 100 public static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a"; 101 102 /** 103 * The default formatting pattern in 24-hour mode. This pattern is used 104 * if {@link #setFormat24Hour(CharSequence)} is called with a null pattern 105 * or if no pattern was specified when creating an instance of this class. 106 * 107 * This default pattern shows only the time, hours and minutes. 108 * 109 * @see #setFormat24Hour(CharSequence) 110 * @see #getFormat24Hour() 111 * @deprecated Let the system use locale-appropriate defaults instead. 112 */ 113 public static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm"; 114 115 private CharSequence mFormat12; 116 private CharSequence mFormat24; 117 118 @ExportedProperty 119 private CharSequence mFormat; 120 @ExportedProperty 121 private boolean mHasSeconds; 122 123 private boolean mAttached; 124 125 private Calendar mTime; 126 private String mTimeZone; 127 128 private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { 129 @Override 130 public void onChange(boolean selfChange) { 131 chooseFormat(); 132 onTimeChanged(); 133 } 134 135 @Override 136 public void onChange(boolean selfChange, Uri uri) { 137 chooseFormat(); 138 onTimeChanged(); 139 } 140 }; 141 142 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 143 @Override 144 public void onReceive(Context context, Intent intent) { 145 if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { 146 final String timeZone = intent.getStringExtra("time-zone"); 147 createTime(timeZone); 148 } 149 onTimeChanged(); 150 } 151 }; 152 153 private final Runnable mTicker = new Runnable() { 154 public void run() { 155 onTimeChanged(); 156 157 long now = SystemClock.uptimeMillis(); 158 long next = now + (1000 - now % 1000); 159 160 getHandler().postAtTime(mTicker, next); 161 } 162 }; 163 164 /** 165 * Creates a new clock using the default patterns 166 * {@link #DEFAULT_FORMAT_24_HOUR} and {@link #DEFAULT_FORMAT_12_HOUR} 167 * respectively for the 24-hour and 12-hour modes. 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 defStyle The default style to apply to this view. If 0, no style 202 * will be applied (beyond what is included in the theme). This may 203 * either be an attribute resource, whose value will be retrieved 204 * from the current theme, or an explicit style resource 205 */ TextClock(Context context, AttributeSet attrs, int defStyle)206 public TextClock(Context context, AttributeSet attrs, int defStyle) { 207 super(context, attrs, defStyle); 208 209 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextClock, defStyle, 0); 210 try { 211 mFormat12 = a.getText(R.styleable.TextClock_format12Hour); 212 mFormat24 = a.getText(R.styleable.TextClock_format24Hour); 213 mTimeZone = a.getString(R.styleable.TextClock_timeZone); 214 } finally { 215 a.recycle(); 216 } 217 218 init(); 219 } 220 init()221 private void init() { 222 if (mFormat12 == null || mFormat24 == null) { 223 LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale); 224 if (mFormat12 == null) { 225 mFormat12 = ld.timeFormat12; 226 } 227 if (mFormat24 == null) { 228 mFormat24 = ld.timeFormat24; 229 } 230 } 231 232 createTime(mTimeZone); 233 // Wait until onAttachedToWindow() to handle the ticker 234 chooseFormat(false); 235 } 236 createTime(String timeZone)237 private void createTime(String timeZone) { 238 if (timeZone != null) { 239 mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); 240 } else { 241 mTime = Calendar.getInstance(); 242 } 243 } 244 245 /** 246 * Returns the formatting pattern used to display the date and/or time 247 * in 12-hour mode. The formatting pattern syntax is described in 248 * {@link DateFormat}. 249 * 250 * @return A {@link CharSequence} or null. 251 * 252 * @see #setFormat12Hour(CharSequence) 253 * @see #is24HourModeEnabled() 254 */ 255 @ExportedProperty getFormat12Hour()256 public CharSequence getFormat12Hour() { 257 return mFormat12; 258 } 259 260 /** 261 * Specifies the formatting pattern used to display the date and/or time 262 * in 12-hour mode. The formatting pattern syntax is described in 263 * {@link DateFormat}. 264 * 265 * If this pattern is set to null, {@link #getFormat24Hour()} will be used 266 * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns 267 * are set to null, {@link #DEFAULT_FORMAT_24_HOUR} and 268 * {@link #DEFAULT_FORMAT_12_HOUR} will be used instead. 269 * 270 * @param format A date/time formatting pattern as described in {@link DateFormat} 271 * 272 * @see #getFormat12Hour() 273 * @see #is24HourModeEnabled() 274 * @see #DEFAULT_FORMAT_12_HOUR 275 * @see DateFormat 276 * 277 * @attr ref android.R.styleable#TextClock_format12Hour 278 */ 279 @RemotableViewMethod setFormat12Hour(CharSequence format)280 public void setFormat12Hour(CharSequence format) { 281 mFormat12 = format; 282 283 chooseFormat(); 284 onTimeChanged(); 285 } 286 287 /** 288 * Returns the formatting pattern used to display the date and/or time 289 * in 24-hour mode. The formatting pattern syntax is described in 290 * {@link DateFormat}. 291 * 292 * @return A {@link CharSequence} or null. 293 * 294 * @see #setFormat24Hour(CharSequence) 295 * @see #is24HourModeEnabled() 296 */ 297 @ExportedProperty getFormat24Hour()298 public CharSequence getFormat24Hour() { 299 return mFormat24; 300 } 301 302 /** 303 * Specifies the formatting pattern used to display the date and/or time 304 * in 24-hour mode. The formatting pattern syntax is described in 305 * {@link DateFormat}. 306 * 307 * If this pattern is set to null, {@link #getFormat12Hour()} will be used 308 * even in 24-hour mode. If both 24-hour and 12-hour formatting patterns 309 * are set to null, {@link #DEFAULT_FORMAT_24_HOUR} and 310 * {@link #DEFAULT_FORMAT_12_HOUR} will be used instead. 311 * 312 * @param format A date/time formatting pattern as described in {@link DateFormat} 313 * 314 * @see #getFormat24Hour() 315 * @see #is24HourModeEnabled() 316 * @see #DEFAULT_FORMAT_24_HOUR 317 * @see DateFormat 318 * 319 * @attr ref android.R.styleable#TextClock_format24Hour 320 */ 321 @RemotableViewMethod setFormat24Hour(CharSequence format)322 public void setFormat24Hour(CharSequence format) { 323 mFormat24 = format; 324 325 chooseFormat(); 326 onTimeChanged(); 327 } 328 329 /** 330 * Indicates whether the system is currently using the 24-hour mode. 331 * 332 * When the system is in 24-hour mode, this view will use the pattern 333 * returned by {@link #getFormat24Hour()}. In 12-hour mode, the pattern 334 * returned by {@link #getFormat12Hour()} is used instead. 335 * 336 * If either one of the formats is null, the other format is used. If 337 * both formats are null, the default values {@link #DEFAULT_FORMAT_12_HOUR} 338 * and {@link #DEFAULT_FORMAT_24_HOUR} are used instead. 339 * 340 * @return true if time should be displayed in 24-hour format, false if it 341 * should be displayed in 12-hour format. 342 * 343 * @see #setFormat12Hour(CharSequence) 344 * @see #getFormat12Hour() 345 * @see #setFormat24Hour(CharSequence) 346 * @see #getFormat24Hour() 347 */ is24HourModeEnabled()348 public boolean is24HourModeEnabled() { 349 return DateFormat.is24HourFormat(getContext()); 350 } 351 352 /** 353 * Indicates which time zone is currently used by this view. 354 * 355 * @return The ID of the current time zone or null if the default time zone, 356 * as set by the user, must be used 357 * 358 * @see TimeZone 359 * @see java.util.TimeZone#getAvailableIDs() 360 * @see #setTimeZone(String) 361 */ getTimeZone()362 public String getTimeZone() { 363 return mTimeZone; 364 } 365 366 /** 367 * Sets the specified time zone to use in this clock. When the time zone 368 * is set through this method, system time zone changes (when the user 369 * sets the time zone in settings for instance) will be ignored. 370 * 371 * @param timeZone The desired time zone's ID as specified in {@link TimeZone} 372 * or null to user the time zone specified by the user 373 * (system time zone) 374 * 375 * @see #getTimeZone() 376 * @see java.util.TimeZone#getAvailableIDs() 377 * @see TimeZone#getTimeZone(String) 378 * 379 * @attr ref android.R.styleable#TextClock_timeZone 380 */ 381 @RemotableViewMethod setTimeZone(String timeZone)382 public void setTimeZone(String timeZone) { 383 mTimeZone = timeZone; 384 385 createTime(timeZone); 386 onTimeChanged(); 387 } 388 389 /** 390 * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} 391 * depending on whether the user has selected 24-hour format. 392 * 393 * Calling this method does not schedule or unschedule the time ticker. 394 */ chooseFormat()395 private void chooseFormat() { 396 chooseFormat(true); 397 } 398 399 /** 400 * Returns the current format string. Always valid after constructor has 401 * finished, and will never be {@code null}. 402 * 403 * @hide 404 */ getFormat()405 public CharSequence getFormat() { 406 return mFormat; 407 } 408 409 /** 410 * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} 411 * depending on whether the user has selected 24-hour format. 412 * 413 * @param handleTicker true if calling this method should schedule/unschedule the 414 * time ticker, false otherwise 415 */ chooseFormat(boolean handleTicker)416 private void chooseFormat(boolean handleTicker) { 417 final boolean format24Requested = is24HourModeEnabled(); 418 419 LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale); 420 421 if (format24Requested) { 422 mFormat = abc(mFormat24, mFormat12, ld.timeFormat24); 423 } else { 424 mFormat = abc(mFormat12, mFormat24, ld.timeFormat12); 425 } 426 427 boolean hadSeconds = mHasSeconds; 428 mHasSeconds = DateFormat.hasSeconds(mFormat); 429 430 if (handleTicker && mAttached && hadSeconds != mHasSeconds) { 431 if (hadSeconds) getHandler().removeCallbacks(mTicker); 432 else mTicker.run(); 433 } 434 } 435 436 /** 437 * Returns a if not null, else return b if not null, else return c. 438 */ abc(CharSequence a, CharSequence b, CharSequence c)439 private static CharSequence abc(CharSequence a, CharSequence b, CharSequence c) { 440 return a == null ? (b == null ? c : b) : a; 441 } 442 443 @Override onAttachedToWindow()444 protected void onAttachedToWindow() { 445 super.onAttachedToWindow(); 446 447 if (!mAttached) { 448 mAttached = true; 449 450 registerReceiver(); 451 registerObserver(); 452 453 createTime(mTimeZone); 454 455 if (mHasSeconds) { 456 mTicker.run(); 457 } else { 458 onTimeChanged(); 459 } 460 } 461 } 462 463 @Override onDetachedFromWindow()464 protected void onDetachedFromWindow() { 465 super.onDetachedFromWindow(); 466 467 if (mAttached) { 468 unregisterReceiver(); 469 unregisterObserver(); 470 471 getHandler().removeCallbacks(mTicker); 472 473 mAttached = false; 474 } 475 } 476 registerReceiver()477 private void registerReceiver() { 478 final IntentFilter filter = new IntentFilter(); 479 480 filter.addAction(Intent.ACTION_TIME_TICK); 481 filter.addAction(Intent.ACTION_TIME_CHANGED); 482 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 483 484 getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); 485 } 486 registerObserver()487 private void registerObserver() { 488 final ContentResolver resolver = getContext().getContentResolver(); 489 resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver); 490 } 491 unregisterReceiver()492 private void unregisterReceiver() { 493 getContext().unregisterReceiver(mIntentReceiver); 494 } 495 unregisterObserver()496 private void unregisterObserver() { 497 final ContentResolver resolver = getContext().getContentResolver(); 498 resolver.unregisterContentObserver(mFormatChangeObserver); 499 } 500 onTimeChanged()501 private void onTimeChanged() { 502 mTime.setTimeInMillis(System.currentTimeMillis()); 503 setText(DateFormat.format(mFormat, mTime)); 504 } 505 } 506