/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import static android.view.ViewDebug.ExportedProperty;
import static android.widget.RemoteViews.RemoteView;

import android.annotation.NonNull;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
import android.view.ViewHierarchyEncoder;

import com.android.internal.R;

import libcore.icu.LocaleData;

import java.util.Calendar;
import java.util.TimeZone;

TextClock can display the current date and/or time as a formatted string.

This view honors the 24-hour format system setting. As such, it is possible and recommended to provide two different formatting patterns: one to display the date/time in 24-hour mode and one to display the date/time in 12-hour mode. Most callers will want to use the defaults, though, which will be appropriate for the user's locale.

It is possible to determine whether the system is currently in 24-hour mode by calling is24HourModeEnabled().

The rules used by this widget to decide how to format the date and time are the following:

  • In 24-hour mode:
    • Use the value returned by getFormat24Hour() when non-null
    • Otherwise, use the value returned by getFormat12Hour() when non-null
    • Otherwise, use a default value appropriate for the user's locale, such as h:mm a
  • In 12-hour mode:
    • Use the value returned by getFormat12Hour() when non-null
    • Otherwise, use the value returned by getFormat24Hour() when non-null
    • Otherwise, use a default value appropriate for the user's locale, such as HH:mm

The CharSequence instances used as formatting patterns when calling either setFormat24Hour(CharSequence) or setFormat12Hour(CharSequence) can contain styling information. To do so, use a Spanned object. Note that if you customize these strings, it is your responsibility to supply strings appropriate for formatting dates and/or times in the user's locale.

@attrref android.R.styleable#TextClock_format12Hour
@attrref android.R.styleable#TextClock_format24Hour
@attrref android.R.styleable#TextClock_timeZone
/** * <p><code>TextClock</code> can display the current date and/or time as * a formatted string.</p> * * <p>This view honors the 24-hour format system setting. As such, it is * possible and recommended to provide two different formatting patterns: * one to display the date/time in 24-hour mode and one to display the * date/time in 12-hour mode. Most callers will want to use the defaults, * though, which will be appropriate for the user's locale.</p> * * <p>It is possible to determine whether the system is currently in * 24-hour mode by calling {@link #is24HourModeEnabled()}.</p> * * <p>The rules used by this widget to decide how to format the date and * time are the following:</p> * <ul> * <li>In 24-hour mode: * <ul> * <li>Use the value returned by {@link #getFormat24Hour()} when non-null</li> * <li>Otherwise, use the value returned by {@link #getFormat12Hour()} when non-null</li> * <li>Otherwise, use a default value appropriate for the user's locale, such as {@code h:mm a}</li> * </ul> * </li> * <li>In 12-hour mode: * <ul> * <li>Use the value returned by {@link #getFormat12Hour()} when non-null</li> * <li>Otherwise, use the value returned by {@link #getFormat24Hour()} when non-null</li> * <li>Otherwise, use a default value appropriate for the user's locale, such as {@code HH:mm}</li> * </ul> * </li> * </ul> * * <p>The {@link CharSequence} instances used as formatting patterns when calling either * {@link #setFormat24Hour(CharSequence)} or {@link #setFormat12Hour(CharSequence)} can * contain styling information. To do so, use a {@link android.text.Spanned} object. * Note that if you customize these strings, it is your responsibility to supply strings * appropriate for formatting dates and/or times in the user's locale.</p> * * @attr ref android.R.styleable#TextClock_format12Hour * @attr ref android.R.styleable#TextClock_format24Hour * @attr ref android.R.styleable#TextClock_timeZone */
@RemoteView public class TextClock extends TextView {
The default formatting pattern in 12-hour mode. This pattern is used if 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 Also:
Deprecated:Let the system use locale-appropriate defaults instead.
/** * The default formatting pattern in 12-hour mode. This pattern 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() * * @deprecated Let the system use locale-appropriate defaults instead. */
@Deprecated public static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a";
The default formatting pattern in 24-hour mode. This pattern is used if 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 Also:
Deprecated:Let the system use locale-appropriate defaults instead.
/** * The default formatting pattern in 24-hour mode. This pattern 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() * * @deprecated Let the system use locale-appropriate defaults instead. */
@Deprecated public static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm"; private CharSequence mFormat12; private CharSequence mFormat24; private CharSequence mDescFormat12; private CharSequence mDescFormat24; @ExportedProperty private CharSequence mFormat; @ExportedProperty private boolean mHasSeconds; private CharSequence mDescFormat; private boolean mRegistered; private boolean mShouldRunTicker; private Calendar mTime; private String mTimeZone; private boolean mShowCurrentUserTime; private ContentObserver mFormatChangeObserver; // Used by tests to stop time change events from triggering the text update private boolean mStopTicking; private class FormatChangeObserver extends ContentObserver { public FormatChangeObserver(Handler handler) { super(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 (mStopTicking) { return; // Test disabled the clock ticks } if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { final String timeZone = intent.getStringExtra("time-zone"); createTime(timeZone); } onTimeChanged(); } }; private final Runnable mTicker = new Runnable() { public void run() { if (mStopTicking) { return; // Test disabled the clock ticks } onTimeChanged(); long now = SystemClock.uptimeMillis(); long next = now + (1000 - now % 1000); getHandler().postAtTime(mTicker, next); } };
Creates a new clock using the default patterns for the current locale.
Params:
  • context – The Context the view is running in, through which it can access the current theme, resources, etc.
/** * Creates a new clock using the default patterns for the current locale. * * @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.
Params:
  • context – The Context the view is running in, through which it can access the current theme, resources, etc.
  • attrs – The attributes of the XML tag that is inflating the view
/** * 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.
Params:
  • context – The Context the view is running in, through which it can access the current theme, resources, etc.
  • attrs – The attributes of the XML tag that is inflating the view
  • defStyleAttr – An attribute in the current theme that contains a reference to a style resource that supplies default values for the view. Can be 0 to not look for defaults.
/** * 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 defStyleAttr An attribute in the current theme that contains a * reference to a style resource that supplies default values for * the view. Can be 0 to not look for defaults. */
public TextClock(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public TextClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.TextClock, defStyleAttr, defStyleRes); try { mFormat12 = a.getText(R.styleable.TextClock_format12Hour); mFormat24 = a.getText(R.styleable.TextClock_format24Hour); mTimeZone = a.getString(R.styleable.TextClock_timeZone); } finally { a.recycle(); } init(); } private void init() { if (mFormat12 == null || mFormat24 == null) { LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale); if (mFormat12 == null) { mFormat12 = ld.timeFormat_hm; } if (mFormat24 == null) { mFormat24 = ld.timeFormat_Hm; } } createTime(mTimeZone); 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 DateFormat.
See Also:
Returns:A CharSequence or null.
/** * Returns the formatting pattern used to display the date and/or time * in 12-hour mode. The formatting pattern syntax is described in * {@link DateFormat}. * * @return A {@link CharSequence} or null. * * @see #setFormat12Hour(CharSequence) * @see #is24HourModeEnabled() */
@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 DateFormat.

If this pattern is set to null, getFormat24Hour() will be used even in 12-hour mode. If both 24-hour and 12-hour formatting patterns are set to null, the default pattern for the current locale will be used instead.

Note: if styling is not needed, it is highly recommended you supply a format string generated by DateFormat.getBestDateTimePattern(Locale, String). This method takes care of generating a format string adapted to the desired locale.

Params:
  • format – A date/time formatting pattern as described in DateFormat
See Also:
@attrref android.R.styleable#TextClock_format12Hour
/** * <p>Specifies the formatting pattern used to display the date and/or time * in 12-hour mode. The formatting pattern syntax is described in * {@link DateFormat}.</p> * * <p>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, the default pattern for the current locale will be used * instead.</p> * * <p><strong>Note:</strong> if styling is not needed, it is highly recommended * you supply a format string generated by * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method * takes care of generating a format string adapted to the desired locale.</p> * * * @param format A date/time formatting pattern as described in {@link DateFormat} * * @see #getFormat12Hour() * @see #is24HourModeEnabled() * @see DateFormat#getBestDateTimePattern(java.util.Locale, String) * @see DateFormat * * @attr ref android.R.styleable#TextClock_format12Hour */
@RemotableViewMethod public void setFormat12Hour(CharSequence format) { mFormat12 = format; chooseFormat(); onTimeChanged(); }
Like setFormat12Hour, but for the content description.
@hide
/** * Like setFormat12Hour, but for the content description. * @hide */
public void setContentDescriptionFormat12Hour(CharSequence format) { mDescFormat12 = 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 DateFormat.
See Also:
Returns:A CharSequence or null.
/** * Returns the formatting pattern used to display the date and/or time * in 24-hour mode. The formatting pattern syntax is described in * {@link DateFormat}. * * @return A {@link CharSequence} or null. * * @see #setFormat24Hour(CharSequence) * @see #is24HourModeEnabled() */
@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 DateFormat.

If this pattern is set to null, getFormat24Hour() will be used even in 12-hour mode. If both 24-hour and 12-hour formatting patterns are set to null, the default pattern for the current locale will be used instead.

Note: if styling is not needed, it is highly recommended you supply a format string generated by DateFormat.getBestDateTimePattern(Locale, String). This method takes care of generating a format string adapted to the desired locale.

Params:
  • format – A date/time formatting pattern as described in DateFormat
See Also:
@attrref android.R.styleable#TextClock_format24Hour
/** * <p>Specifies the formatting pattern used to display the date and/or time * in 24-hour mode. The formatting pattern syntax is described in * {@link DateFormat}.</p> * * <p>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, the default pattern for the current locale will be used * instead.</p> * * <p><strong>Note:</strong> if styling is not needed, it is highly recommended * you supply a format string generated by * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method * takes care of generating a format string adapted to the desired locale.</p> * * @param format A date/time formatting pattern as described in {@link DateFormat} * * @see #getFormat24Hour() * @see #is24HourModeEnabled() * @see DateFormat#getBestDateTimePattern(java.util.Locale, String) * @see DateFormat * * @attr ref android.R.styleable#TextClock_format24Hour */
@RemotableViewMethod public void setFormat24Hour(CharSequence format) { mFormat24 = format; chooseFormat(); onTimeChanged(); }
Like setFormat24Hour, but for the content description.
@hide
/** * Like setFormat24Hour, but for the content description. * @hide */
public void setContentDescriptionFormat24Hour(CharSequence format) { mDescFormat24 = format; chooseFormat(); onTimeChanged(); }
Sets whether this clock should always track the current user and not the user of the current process. This is used for single instance processes like the systemUI who need to display time for different users.
@hide
/** * Sets whether this clock should always track the current user and not the user of the * current process. This is used for single instance processes like the systemUI who need * to display time for different users. * * @hide */
public void setShowCurrentUserTime(boolean showCurrentUserTime) { mShowCurrentUserTime = showCurrentUserTime; chooseFormat(); onTimeChanged(); unregisterObserver(); registerObserver(); }
Update the displayed time if necessary and invalidate the view.
@hide
/** * Update the displayed time if necessary and invalidate the view. * @hide */
public void refresh() { onTimeChanged(); invalidate(); }
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 getFormat24Hour(). In 12-hour mode, the pattern returned by getFormat12Hour() is used instead. If either one of the formats is null, the other format is used. If both formats are null, the default formats for the current locale are used.
See Also:
Returns:true if time should be displayed in 24-hour format, false if it should be displayed in 12-hour format.
/** * 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 formats for the current locale are used. * * @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() { if (mShowCurrentUserTime) { return DateFormat.is24HourFormat(getContext(), ActivityManager.getCurrentUser()); } else { return DateFormat.is24HourFormat(getContext()); } }
Indicates which time zone is currently used by this view.
See Also:
Returns:The ID of the current time zone or null if the default time zone, as set by the user, must be used
/** * 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.
Params:
  • timeZone – The desired time zone's ID as specified in TimeZone or null to user the time zone specified by the user (system time zone)
See Also:
@attrref android.R.styleable#TextClock_timeZone
/** * 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) * * @attr ref android.R.styleable#TextClock_timeZone */
@RemotableViewMethod public void setTimeZone(String timeZone) { mTimeZone = timeZone; createTime(timeZone); onTimeChanged(); }
Returns the current format string. Always valid after constructor has finished, and will never be null.
@hide
/** * Returns the current format string. Always valid after constructor has * finished, and will never be {@code null}. * * @hide */
public CharSequence getFormat() { return mFormat; }
Selects either one of getFormat12Hour() or getFormat24Hour() depending on whether the user has selected 24-hour format.
/** * 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(); LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale); if (format24Requested) { mFormat = abc(mFormat24, mFormat12, ld.timeFormat_Hm); mDescFormat = abc(mDescFormat24, mDescFormat12, mFormat); } else { mFormat = abc(mFormat12, mFormat24, ld.timeFormat_hm); mDescFormat = abc(mDescFormat12, mDescFormat24, mFormat); } boolean hadSeconds = mHasSeconds; mHasSeconds = DateFormat.hasSeconds(mFormat); if (mShouldRunTicker && hadSeconds != mHasSeconds) { if (hadSeconds) getHandler().removeCallbacks(mTicker); else mTicker.run(); } }
Returns a if not null, else return b if not null, else return c.
/** * 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 (!mRegistered) { mRegistered = true; registerReceiver(); registerObserver(); createTime(mTimeZone); } } @Override public void onVisibilityAggregated(boolean isVisible) { super.onVisibilityAggregated(isVisible); if (!mShouldRunTicker && isVisible) { mShouldRunTicker = true; if (mHasSeconds) { mTicker.run(); } else { onTimeChanged(); } } else if (mShouldRunTicker && !isVisible) { mShouldRunTicker = false; getHandler().removeCallbacks(mTicker); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mRegistered) { unregisterReceiver(); unregisterObserver(); mRegistered = false; } }
Used by tests to stop the clock tick from updating the text.
@hide
/** * Used by tests to stop the clock tick from updating the text. * @hide */
@TestApi public void disableClockTick() { mStopTicking = true; } 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); // OK, this is gross but needed. This class is supported by the // remote views mechanism and as a part of that the remote views // can be inflated by a context for another user without the app // having interact users permission - just for loading resources. // For example, when adding widgets from a managed profile to the // home screen. Therefore, we register the receiver as the user // the app is running as not the one the context is for. getContext().registerReceiverAsUser(mIntentReceiver, android.os.Process.myUserHandle(), filter, null, getHandler()); } private void registerObserver() { if (mRegistered) { if (mFormatChangeObserver == null) { mFormatChangeObserver = new FormatChangeObserver(getHandler()); } final ContentResolver resolver = getContext().getContentResolver(); Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24); if (mShowCurrentUserTime) { resolver.registerContentObserver(uri, true, mFormatChangeObserver, UserHandle.USER_ALL); } else { resolver.registerContentObserver(uri, true, mFormatChangeObserver); } } } private void unregisterReceiver() { getContext().unregisterReceiver(mIntentReceiver); } private void unregisterObserver() { if (mFormatChangeObserver != null) { final ContentResolver resolver = getContext().getContentResolver(); resolver.unregisterContentObserver(mFormatChangeObserver); } }
Update the displayed time if this view and its ancestors and window is visible
/** * Update the displayed time if this view and its ancestors and window is visible */
private void onTimeChanged() { // mShouldRunTicker always equals the last value passed into onVisibilityAggregated if (mShouldRunTicker) { mTime.setTimeInMillis(System.currentTimeMillis()); setText(DateFormat.format(mFormat, mTime)); setContentDescription(DateFormat.format(mDescFormat, mTime)); } }
@hide
/** @hide */
@Override protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { super.encodeProperties(stream); CharSequence s = getFormat12Hour(); stream.addProperty("format12Hour", s == null ? null : s.toString()); s = getFormat24Hour(); stream.addProperty("format24Hour", s == null ? null : s.toString()); stream.addProperty("format", mFormat == null ? null : mFormat.toString()); stream.addProperty("hasSeconds", mHasSeconds); } }