blob: a564c96ee2202428fc70fe08b3914f194796af34 [file] [log] [blame]
Romain Guy3d1728c2012-10-31 20:31:58 -07001/*
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.content.BroadcastReceiver;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.res.TypedArray;
25import android.database.ContentObserver;
26import android.net.Uri;
27import android.os.Handler;
28import android.os.SystemClock;
29import android.provider.Settings;
30import android.text.format.DateFormat;
31import android.util.AttributeSet;
Romain Guya9cfe672012-11-05 14:16:32 -080032import android.view.RemotableViewMethod;
Romain Guy3d1728c2012-10-31 20:31:58 -070033
34import com.android.internal.R;
35
36import java.util.Calendar;
37import java.util.TimeZone;
38
Elliott Hughescdafd372013-03-18 17:21:33 -070039import libcore.icu.LocaleData;
40
Romain Guy3d1728c2012-10-31 20:31:58 -070041import static android.view.ViewDebug.ExportedProperty;
42import 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>
Elliott Hughescdafd372013-03-18 17:21:33 -070047 *
Romain Guy3d1728c2012-10-31 20:31:58 -070048 * <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
Elliott Hughescdafd372013-03-18 17:21:33 -070051 * 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 *
Romain Guy3d1728c2012-10-31 20:31:58 -070054 * <p>It is possible to determine whether the system is currently in
55 * 24-hour mode by calling {@link #is24HourModeEnabled()}.</p>
Elliott Hughescdafd372013-03-18 17:21:33 -070056 *
Romain Guy3d1728c2012-10-31 20:31:58 -070057 * <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>
Elliott Hughescdafd372013-03-18 17:21:33 -070064 * <li>Otherwise, use a default value appropriate for the user's locale, such as {@code h:mm a}</li>
Romain Guy3d1728c2012-10-31 20:31:58 -070065 * </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>
Elliott Hughescdafd372013-03-18 17:21:33 -070071 * <li>Otherwise, use a default value appropriate for the user's locale, such as {@code HH:mm}</li>
Romain Guy3d1728c2012-10-31 20:31:58 -070072 * </ul>
73 * </li>
74 * </ul>
Elliott Hughescdafd372013-03-18 17:21:33 -070075 *
Romain Guy3d1728c2012-10-31 20:31:58 -070076 * <p>The {@link CharSequence} instances used as formatting patterns when calling either
77 * {@link #setFormat24Hour(CharSequence)} or {@link #setFormat12Hour(CharSequence)} can
Elliott Hughescdafd372013-03-18 17:21:33 -070078 * 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 *
Romain Guy3d1728c2012-10-31 20:31:58 -070082 * @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
87public class TextClock extends TextView {
88 /**
Elliott Hughescdafd372013-03-18 17:21:33 -070089 * The default formatting pattern in 12-hour mode. This pattern is used
Romain Guy3d1728c2012-10-31 20:31:58 -070090 * if {@link #setFormat12Hour(CharSequence)} is called with a null pattern
91 * or if no pattern was specified when creating an instance of this class.
Elliott Hughescdafd372013-03-18 17:21:33 -070092 *
Romain Guy3d1728c2012-10-31 20:31:58 -070093 * This default pattern shows only the time, hours and minutes, and an am/pm
94 * indicator.
95 *
96 * @see #setFormat12Hour(CharSequence)
97 * @see #getFormat12Hour()
Elliott Hughescdafd372013-03-18 17:21:33 -070098 * @deprecated Let the system use locale-appropriate defaults instead.
Romain Guy3d1728c2012-10-31 20:31:58 -070099 */
Elliott Hughescdafd372013-03-18 17:21:33 -0700100 public static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a";
Romain Guy3d1728c2012-10-31 20:31:58 -0700101
102 /**
Elliott Hughescdafd372013-03-18 17:21:33 -0700103 * The default formatting pattern in 24-hour mode. This pattern is used
Romain Guy3d1728c2012-10-31 20:31:58 -0700104 * 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.
Elliott Hughescdafd372013-03-18 17:21:33 -0700108 *
109 * @see #setFormat24Hour(CharSequence)
110 * @see #getFormat24Hour()
111 * @deprecated Let the system use locale-appropriate defaults instead.
Romain Guy3d1728c2012-10-31 20:31:58 -0700112 */
Elliott Hughescdafd372013-03-18 17:21:33 -0700113 public static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm";
Romain Guy3d1728c2012-10-31 20:31:58 -0700114
Elliott Hughescdafd372013-03-18 17:21:33 -0700115 private CharSequence mFormat12;
116 private CharSequence mFormat24;
Romain Guy3d1728c2012-10-31 20:31:58 -0700117
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) {
Romain Guya76f7db2012-11-06 17:35:23 -0800145 if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
146 final String timeZone = intent.getStringExtra("time-zone");
147 createTime(timeZone);
Romain Guy3d1728c2012-10-31 20:31:58 -0700148 }
Romain Guya76f7db2012-11-06 17:35:23 -0800149 onTimeChanged();
Romain Guy3d1728c2012-10-31 20:31:58 -0700150 }
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.
Elliott Hughescdafd372013-03-18 17:21:33 -0700168 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700169 * @param context The Context the view is running in, through which it can
170 * access the current theme, resources, etc.
171 */
172 @SuppressWarnings("UnusedDeclaration")
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.
Elliott Hughescdafd372013-03-18 17:21:33 -0700181 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700182 * 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")
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 */
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 {
Elliott Hughescdafd372013-03-18 17:21:33 -0700211 mFormat12 = a.getText(R.styleable.TextClock_format12Hour);
212 mFormat24 = a.getText(R.styleable.TextClock_format24Hour);
Romain Guy3d1728c2012-10-31 20:31:58 -0700213 mTimeZone = a.getString(R.styleable.TextClock_timeZone);
214 } finally {
215 a.recycle();
216 }
217
218 init();
219 }
220
221 private void init() {
Elliott Hughescdafd372013-03-18 17:21:33 -0700222 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
Romain Guy3d1728c2012-10-31 20:31:58 -0700232 createTime(mTimeZone);
233 // Wait until onAttachedToWindow() to handle the ticker
234 chooseFormat(false);
235 }
236
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}.
Elliott Hughescdafd372013-03-18 17:21:33 -0700249 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700250 * @return A {@link CharSequence} or null.
Elliott Hughescdafd372013-03-18 17:21:33 -0700251 *
252 * @see #setFormat12Hour(CharSequence)
253 * @see #is24HourModeEnabled()
Romain Guy3d1728c2012-10-31 20:31:58 -0700254 */
255 @ExportedProperty
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}
Elliott Hughescdafd372013-03-18 17:21:33 -0700271 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700272 * @see #getFormat12Hour()
273 * @see #is24HourModeEnabled()
274 * @see #DEFAULT_FORMAT_12_HOUR
275 * @see DateFormat
Elliott Hughescdafd372013-03-18 17:21:33 -0700276 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700277 * @attr ref android.R.styleable#TextClock_format12Hour
278 */
Romain Guya9cfe672012-11-05 14:16:32 -0800279 @RemotableViewMethod
Romain Guy3d1728c2012-10-31 20:31:58 -0700280 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
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}.
Elliott Hughescdafd372013-03-18 17:21:33 -0700306 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700307 * 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()
Elliott Hughescdafd372013-03-18 17:21:33 -0700315 * @see #is24HourModeEnabled()
Romain Guy3d1728c2012-10-31 20:31:58 -0700316 * @see #DEFAULT_FORMAT_24_HOUR
317 * @see DateFormat
318 *
319 * @attr ref android.R.styleable#TextClock_format24Hour
320 */
Romain Guya9cfe672012-11-05 14:16:32 -0800321 @RemotableViewMethod
Romain Guy3d1728c2012-10-31 20:31:58 -0700322 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.
Elliott Hughescdafd372013-03-18 17:21:33 -0700331 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700332 * 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.
Elliott Hughescdafd372013-03-18 17:21:33 -0700335 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700336 * 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.
Elliott Hughescdafd372013-03-18 17:21:33 -0700339 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700340 * @return true if time should be displayed in 24-hour format, false if it
341 * should be displayed in 12-hour format.
Elliott Hughescdafd372013-03-18 17:21:33 -0700342 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700343 * @see #setFormat12Hour(CharSequence)
Elliott Hughescdafd372013-03-18 17:21:33 -0700344 * @see #getFormat12Hour()
Romain Guy3d1728c2012-10-31 20:31:58 -0700345 * @see #setFormat24Hour(CharSequence)
Elliott Hughescdafd372013-03-18 17:21:33 -0700346 * @see #getFormat24Hour()
Romain Guy3d1728c2012-10-31 20:31:58 -0700347 */
348 public boolean is24HourModeEnabled() {
349 return DateFormat.is24HourFormat(getContext());
350 }
351
352 /**
353 * Indicates which time zone is currently used by this view.
Elliott Hughescdafd372013-03-18 17:21:33 -0700354 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700355 * @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()
Elliott Hughescdafd372013-03-18 17:21:33 -0700360 * @see #setTimeZone(String)
Romain Guy3d1728c2012-10-31 20:31:58 -0700361 */
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 */
Romain Guya9cfe672012-11-05 14:16:32 -0800381 @RemotableViewMethod
Romain Guy3d1728c2012-10-31 20:31:58 -0700382 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.
Elliott Hughescdafd372013-03-18 17:21:33 -0700392 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700393 * Calling this method does not schedule or unschedule the time ticker.
394 */
395 private void chooseFormat() {
396 chooseFormat(true);
397 }
398
399 /**
Jeff Sharkey06c5f8a2012-12-04 09:53:44 -0800400 * Returns the current format string. Always valid after constructor has
401 * finished, and will never be {@code null}.
402 *
403 * @hide
404 */
405 public CharSequence getFormat() {
406 return mFormat;
407 }
408
409 /**
Romain Guy3d1728c2012-10-31 20:31:58 -0700410 * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
411 * depending on whether the user has selected 24-hour format.
Elliott Hughescdafd372013-03-18 17:21:33 -0700412 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700413 * @param handleTicker true if calling this method should schedule/unschedule the
414 * time ticker, false otherwise
415 */
416 private void chooseFormat(boolean handleTicker) {
417 final boolean format24Requested = is24HourModeEnabled();
418
Elliott Hughescdafd372013-03-18 17:21:33 -0700419 LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale);
420
Romain Guy3d1728c2012-10-31 20:31:58 -0700421 if (format24Requested) {
Elliott Hughescdafd372013-03-18 17:21:33 -0700422 mFormat = abc(mFormat24, mFormat12, ld.timeFormat24);
Romain Guy3d1728c2012-10-31 20:31:58 -0700423 } else {
Elliott Hughescdafd372013-03-18 17:21:33 -0700424 mFormat = abc(mFormat12, mFormat24, ld.timeFormat12);
Romain Guy3d1728c2012-10-31 20:31:58 -0700425 }
426
427 boolean hadSeconds = mHasSeconds;
428 mHasSeconds = DateFormat.hasSeconds(mFormat);
429
Romain Guya76f7db2012-11-06 17:35:23 -0800430 if (handleTicker && mAttached && hadSeconds != mHasSeconds) {
431 if (hadSeconds) getHandler().removeCallbacks(mTicker);
432 else mTicker.run();
Romain Guy3d1728c2012-10-31 20:31:58 -0700433 }
434 }
435
436 /**
437 * Returns a if not null, else return b if not null, else return c.
438 */
439 private static CharSequence abc(CharSequence a, CharSequence b, CharSequence c) {
440 return a == null ? (b == null ? c : b) : a;
441 }
442
443 @Override
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
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
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
487 private void registerObserver() {
488 final ContentResolver resolver = getContext().getContentResolver();
489 resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver);
490 }
491
492 private void unregisterReceiver() {
493 getContext().unregisterReceiver(mIntentReceiver);
494 }
495
496 private void unregisterObserver() {
497 final ContentResolver resolver = getContext().getContentResolver();
498 resolver.unregisterContentObserver(mFormatChangeObserver);
499 }
500
501 private void onTimeChanged() {
502 mTime.setTimeInMillis(System.currentTimeMillis());
503 setText(DateFormat.format(mFormat, mTime));
504 }
505}