// Copyright 2013 Google Inc. All Rights Reserved. package us.nineworlds.serenity.ui.views; import android.content.*; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.provider.Settings; import android.text.format.DateFormat; import android.util.AttributeSet; import android.view.ViewDebug; import android.widget.TextView; import java.util.Calendar; import java.util.TimeZone; /** * Shamelessly stolen from {@link android.widget.TextClock} this class can be used in API < 17. * Some stuff was removed for simplicity like XML attribute support and second-ticker support. * Second support was removed because the entire purpose of this class is to display a time of day * without seconds as the pre API 17 {@link android.widget.DigitalClock} doesn't have this option. */ public class TextClock extends TextView { /** * The default formatting pattern in 12-hour mode. This pattenr is used * if {@link #setFormat12Hour(CharSequence)} is called with a null pattern * or if no pattern was specified when creating an instance of this class. * * This default pattern shows only the time, hours and minutes, and an am/pm * indicator. * * @see #setFormat12Hour(CharSequence) * @see #getFormat12Hour() */ public static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm aa"; /** * The default formatting pattern in 24-hour mode. This pattenr is used * if {@link #setFormat24Hour(CharSequence)} is called with a null pattern * or if no pattern was specified when creating an instance of this class. * * This default pattern shows only the time, hours and minutes. * * @see #setFormat24Hour(CharSequence) * @see #getFormat24Hour() */ public static final CharSequence DEFAULT_FORMAT_24_HOUR = "k:mm"; private CharSequence mFormat12 = DEFAULT_FORMAT_12_HOUR; private CharSequence mFormat24 = DEFAULT_FORMAT_24_HOUR; @ViewDebug.ExportedProperty private CharSequence mFormat; @ViewDebug.ExportedProperty private boolean mAttached; private Calendar mTime; private String mTimeZone; private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { chooseFormat(); onTimeChanged(); } @Override public void onChange(boolean selfChange, Uri uri) { chooseFormat(); onTimeChanged(); } }; private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { final String timeZone = intent.getStringExtra("time-zone"); createTime(timeZone); } onTimeChanged(); } }; /** * Creates a new clock using the default patterns * {@link #DEFAULT_FORMAT_24_HOUR} and {@link #DEFAULT_FORMAT_12_HOUR} * respectively for the 24-hour and 12-hour modes. * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. */ @SuppressWarnings("UnusedDeclaration") public TextClock(Context context) { super(context); init(); } /** * Creates a new clock inflated from XML. This object's properties are * intialized from the attributes specified in XML. * * This constructor uses a default style of 0, so the only attribute values * applied are those in the Context's Theme and the given AttributeSet. * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view */ @SuppressWarnings("UnusedDeclaration") public TextClock(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * Creates a new clock inflated from XML. This object's properties are * intialized from the attributes specified in XML. * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view * @param defStyle The default style to apply to this view. If 0, no style * will be applied (beyond what is included in the theme). This may * either be an attribute resource, whose value will be retrieved * from the current theme, or an explicit style resource */ public TextClock(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { createTime(mTimeZone); // Wait until onAttachedToWindow() to handle the ticker chooseFormat(); } private void createTime(String timeZone) { if (timeZone != null) { mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); } else { mTime = Calendar.getInstance(); } } /** * Returns the formatting pattern used to display the date and/or time * in 12-hour mode. The formatting pattern syntax is described in * {@link android.text.format.DateFormat}. * * @return A {@link CharSequence} or null. * * @see #setFormat12Hour(CharSequence) * @see #is24HourModeEnabled() */ @ViewDebug.ExportedProperty public CharSequence getFormat12Hour() { return mFormat12; } /** * Specifies the formatting pattern used to display the date and/or time * in 12-hour mode. The formatting pattern syntax is described in * {@link android.text.format.DateFormat}. * * If this pattern is set to null, {@link #getFormat24Hour()} will be used * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns * are set to null, {@link #DEFAULT_FORMAT_24_HOUR} and * {@link #DEFAULT_FORMAT_12_HOUR} will be used instead. * * @param format A date/time formatting pattern as described in {@link android.text.format.DateFormat} * * @see #getFormat12Hour() * @see #is24HourModeEnabled() * @see #DEFAULT_FORMAT_12_HOUR * @see android.text.format.DateFormat */ public void setFormat12Hour(CharSequence format) { mFormat12 = format; chooseFormat(); onTimeChanged(); } /** * Returns the formatting pattern used to display the date and/or time * in 24-hour mode. The formatting pattern syntax is described in * {@link android.text.format.DateFormat}. * * @return A {@link CharSequence} or null. * * @see #setFormat24Hour(CharSequence) * @see #is24HourModeEnabled() */ @ViewDebug.ExportedProperty public CharSequence getFormat24Hour() { return mFormat24; } /** * Specifies the formatting pattern used to display the date and/or time * in 24-hour mode. The formatting pattern syntax is described in * {@link android.text.format.DateFormat}. * * If this pattern is set to null, {@link #getFormat12Hour()} will be used * even in 24-hour mode. If both 24-hour and 12-hour formatting patterns * are set to null, {@link #DEFAULT_FORMAT_24_HOUR} and * {@link #DEFAULT_FORMAT_12_HOUR} will be used instead. * * @param format A date/time formatting pattern as described in {@link android.text.format.DateFormat} * * @see #getFormat24Hour() * @see #is24HourModeEnabled() * @see #DEFAULT_FORMAT_24_HOUR * @see android.text.format.DateFormat */ public void setFormat24Hour(CharSequence format) { mFormat24 = format; chooseFormat(); onTimeChanged(); } /** * Indicates whether the system is currently using the 24-hour mode. * * When the system is in 24-hour mode, this view will use the pattern * returned by {@link #getFormat24Hour()}. In 12-hour mode, the pattern * returned by {@link #getFormat12Hour()} is used instead. * * If either one of the formats is null, the other format is used. If * both formats are null, the default values {@link #DEFAULT_FORMAT_12_HOUR} * and {@link #DEFAULT_FORMAT_24_HOUR} are used instead. * * @return true if time should be displayed in 24-hour format, false if it * should be displayed in 12-hour format. * * @see #setFormat12Hour(CharSequence) * @see #getFormat12Hour() * @see #setFormat24Hour(CharSequence) * @see #getFormat24Hour() */ public boolean is24HourModeEnabled() { return DateFormat.is24HourFormat(getContext()); } /** * Indicates which time zone is currently used by this view. * * @return The ID of the current time zone or null if the default time zone, * as set by the user, must be used * * @see TimeZone * @see java.util.TimeZone#getAvailableIDs() * @see #setTimeZone(String) */ public String getTimeZone() { return mTimeZone; } /** * Sets the specified time zone to use in this clock. When the time zone * is set through this method, system time zone changes (when the user * sets the time zone in settings for instance) will be ignored. * * @param timeZone The desired time zone's ID as specified in {@link TimeZone} * or null to user the time zone specified by the user * (system time zone) * * @see #getTimeZone() * @see java.util.TimeZone#getAvailableIDs() * @see TimeZone#getTimeZone(String) */ public void setTimeZone(String timeZone) { mTimeZone = timeZone; createTime(timeZone); onTimeChanged(); } /** * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} * depending on whether the user has selected 24-hour format. * */ private void chooseFormat() { final boolean format24Requested = is24HourModeEnabled(); if (format24Requested) { mFormat = abc(mFormat24, mFormat12, DEFAULT_FORMAT_24_HOUR); } else { mFormat = abc(mFormat12, mFormat24, DEFAULT_FORMAT_12_HOUR); } } /** * Returns a if not null, else return b if not null, else return c. */ private static CharSequence abc(CharSequence a, CharSequence b, CharSequence c) { return a == null ? (b == null ? c : b) : a; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (!mAttached) { mAttached = true; registerReceiver(); registerObserver(); createTime(mTimeZone); onTimeChanged(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mAttached) { unregisterReceiver(); unregisterObserver(); mAttached = false; } } private void registerReceiver() { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIME_TICK); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); } private void registerObserver() { final ContentResolver resolver = getContext().getContentResolver(); resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver); } private void unregisterReceiver() { getContext().unregisterReceiver(mIntentReceiver); } private void unregisterObserver() { final ContentResolver resolver = getContext().getContentResolver(); resolver.unregisterContentObserver(mFormatChangeObserver); } private void onTimeChanged() { mTime.setTimeInMillis(System.currentTimeMillis()); setText(DateFormat.format(mFormat, mTime)); } }