| /* |
| * 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.compat.annotation.UnsupportedAppUsage; |
| 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 android.view.inspector.InspectableProperty; |
| |
| import com.android.internal.R; |
| |
| import libcore.icu.LocaleData; |
| |
| import java.util.Calendar; |
| import java.util.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 {@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 {@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); |
| } else if (!mShouldRunTicker && (Intent.ACTION_TIME_TICK.equals(intent.getAction()) |
| || Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))) { |
| return; |
| } |
| 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. |
| * |
| * @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 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); |
| saveAttributeDataForStyleable(context, R.styleable.TextClock, |
| attrs, a, 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 |
| * {@link DateFormat}. |
| * |
| * @return A {@link CharSequence} or null. |
| * |
| * @see #setFormat12Hour(CharSequence) |
| * @see #is24HourModeEnabled() |
| */ |
| @InspectableProperty |
| @ExportedProperty |
| public CharSequence getFormat12Hour() { |
| return mFormat12; |
| } |
| |
| /** |
| * <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 |
| */ |
| 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 |
| * {@link DateFormat}. |
| * |
| * @return A {@link CharSequence} or null. |
| * |
| * @see #setFormat24Hour(CharSequence) |
| * @see #is24HourModeEnabled() |
| */ |
| @InspectableProperty |
| @ExportedProperty |
| public CharSequence getFormat24Hour() { |
| return mFormat24; |
| } |
| |
| /** |
| * <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 |
| */ |
| 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 |
| */ |
| public void setShowCurrentUserTime(boolean showCurrentUserTime) { |
| mShowCurrentUserTime = showCurrentUserTime; |
| |
| chooseFormat(); |
| onTimeChanged(); |
| unregisterObserver(); |
| registerObserver(); |
| } |
| |
| /** |
| * 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 {@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() |
| */ |
| @InspectableProperty(hasAttributeId = false) |
| 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. |
| * |
| * @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) |
| */ |
| @InspectableProperty |
| 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) |
| * |
| * @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 {@code null}. |
| * |
| * @hide |
| */ |
| @UnsupportedAppUsage |
| public CharSequence getFormat() { |
| return mFormat; |
| } |
| |
| /** |
| * 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. |
| */ |
| 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 |
| */ |
| @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 { |
| // UserHandle.myUserId() is 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 ContentObserver with the user |
| // the app is running (e.g. the launcher) and not the user of the |
| // context (e.g. the widget's profile). |
| resolver.registerContentObserver(uri, true, |
| mFormatChangeObserver, UserHandle.myUserId()); |
| } |
| } |
| } |
| |
| 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 |
| */ |
| @UnsupportedAppUsage |
| private void onTimeChanged() { |
| mTime.setTimeInMillis(System.currentTimeMillis()); |
| setText(DateFormat.format(mFormat, mTime)); |
| setContentDescription(DateFormat.format(mDescFormat, mTime)); |
| } |
| |
| /** @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); |
| } |
| } |