A new clock widget to create lock screen appwidgets

This new widget replaces DigitalClock. It listens to all the correct
system events and offer the ability to customize the formatting
patterns in 12-hour and 24-hour modes. It also supports fixed
time zones to create world clocks.

One more step towards becoming ClockOS!

Change-Id: I677e5dfca8cd8c8d1f8c49e54d7507f4d1885bf4
This commit is contained in:
Romain Guy
2012-10-31 20:31:58 -07:00
parent ac14efce4b
commit 3d1728c03a
8 changed files with 651 additions and 50 deletions

View File

@ -249,12 +249,13 @@ public class DateFormat {
synchronized (sLocaleLock) {
sIs24HourLocale = locale;
sIs24Hour = !value.equals("12");
sIs24Hour = value.equals("24");
}
return sIs24Hour;
}
boolean b24 = !(value == null || value.equals("12"));
return b24;
return value.equals("24");
}
/**
@ -263,7 +264,7 @@ public class DateFormat {
* @param context the application context
* @return the {@link java.text.DateFormat} object that properly formats the time.
*/
public static final java.text.DateFormat getTimeFormat(Context context) {
public static java.text.DateFormat getTimeFormat(Context context) {
boolean b24 = is24HourFormat(context);
int res;
@ -283,7 +284,7 @@ public class DateFormat {
* @param context the application context
* @return the {@link java.text.DateFormat} object that properly formats the date.
*/
public static final java.text.DateFormat getDateFormat(Context context) {
public static java.text.DateFormat getDateFormat(Context context) {
String value = Settings.System.getString(context.getContentResolver(),
Settings.System.DATE_FORMAT);
@ -353,7 +354,7 @@ public class DateFormat {
* @param context the application context
* @return the {@link java.text.DateFormat} object that formats the date in long form.
*/
public static final java.text.DateFormat getLongDateFormat(Context context) {
public static java.text.DateFormat getLongDateFormat(Context context) {
return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG);
}
@ -363,7 +364,7 @@ public class DateFormat {
* @param context the application context
* @return the {@link java.text.DateFormat} object that formats the date in long form.
*/
public static final java.text.DateFormat getMediumDateFormat(Context context) {
public static java.text.DateFormat getMediumDateFormat(Context context) {
return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM);
}
@ -376,7 +377,7 @@ public class DateFormat {
* not just the day, month, and year, and not necessarily in the same
* order returned here.
*/
public static final char[] getDateFormatOrder(Context context) {
public static char[] getDateFormatOrder(Context context) {
char[] order = new char[] {DATE, MONTH, YEAR};
String value = getDateFormatString(context);
int index = 0;
@ -420,7 +421,7 @@ public class DateFormat {
* @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT
* @return a {@link CharSequence} containing the requested text
*/
public static final CharSequence format(CharSequence inFormat, long inTimeInMillis) {
public static CharSequence format(CharSequence inFormat, long inTimeInMillis) {
return format(inFormat, new Date(inTimeInMillis));
}
@ -431,7 +432,7 @@ public class DateFormat {
* @param inDate the date to format
* @return a {@link CharSequence} containing the requested text
*/
public static final CharSequence format(CharSequence inFormat, Date inDate) {
public static CharSequence format(CharSequence inFormat, Date inDate) {
Calendar c = new GregorianCalendar();
c.setTime(inDate);
@ -439,6 +440,68 @@ public class DateFormat {
return format(inFormat, c);
}
/**
* Indicates whether the specified format string contains seconds.
*
* Always returns false if the input format is null.
*
* @param inFormat the format string, as described in {@link android.text.format.DateFormat}
*
* @return true if the format string contains {@link #SECONDS}, false otherwise
*
* @hide
*/
public static boolean hasSeconds(CharSequence inFormat) {
if (inFormat == null) return false;
final int length = inFormat.length();
int c;
int count;
for (int i = 0; i < length; i += count) {
count = 1;
c = inFormat.charAt(i);
if (c == QUOTE) {
count = skipQuotedText(inFormat, i, length);
} else if (c == SECONDS) {
return true;
}
}
return false;
}
private static int skipQuotedText(CharSequence s, int i, int len) {
if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
return 2;
}
int count = 1;
// skip leading quote
i++;
while (i < len) {
char c = s.charAt(i);
if (c == QUOTE) {
count++;
// QUOTEQUOTE -> QUOTE
if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
i++;
} else {
break;
}
} else {
i++;
count++;
}
}
return count;
}
/**
* Given a format string and a {@link java.util.Calendar} object, returns a CharSequence
* containing the requested date.
@ -446,7 +509,7 @@ public class DateFormat {
* @param inDate the date to format
* @return a {@link CharSequence} containing the requested text
*/
public static final CharSequence format(CharSequence inFormat, Calendar inDate) {
public static CharSequence format(CharSequence inFormat, Calendar inDate) {
SpannableStringBuilder s = new SpannableStringBuilder(inFormat);
int c;
int count;
@ -545,7 +608,7 @@ public class DateFormat {
return s.toString();
}
private static final String getMonthString(Calendar inDate, int count, int kind) {
private static String getMonthString(Calendar inDate, int count, int kind) {
boolean standalone = (kind == STANDALONE_MONTH);
int month = inDate.get(Calendar.MONTH);
@ -563,7 +626,7 @@ public class DateFormat {
}
}
private static final String getTimeZoneString(Calendar inDate, int count) {
private static String getTimeZoneString(Calendar inDate, int count) {
TimeZone tz = inDate.getTimeZone();
if (count < 2) { // FIXME: shouldn't this be <= 2 ?
@ -576,7 +639,7 @@ public class DateFormat {
}
}
private static final String formatZoneOffset(int offset, int count) {
private static String formatZoneOffset(int offset, int count) {
offset /= 1000; // milliseconds to seconds
StringBuilder tb = new StringBuilder();
@ -595,13 +658,13 @@ public class DateFormat {
return tb.toString();
}
private static final String getYearString(Calendar inDate, int count) {
private static String getYearString(Calendar inDate, int count) {
int year = inDate.get(Calendar.YEAR);
return (count <= 2) ? zeroPad(year % 100, 2)
: String.format(Locale.getDefault(), "%d", year);
}
private static final int appendQuotedText(SpannableStringBuilder s, int i, int len) {
private static int appendQuotedText(SpannableStringBuilder s, int i, int len) {
if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
s.delete(i, i + 1);
return 1;
@ -638,7 +701,7 @@ public class DateFormat {
return count;
}
private static final String zeroPad(int inValue, int inMinDigits) {
private static String zeroPad(int inValue, int inMinDigits) {
return String.format(Locale.getDefault(), "%0" + inMinDigits + "d", inValue);
}
}

View File

@ -17697,7 +17697,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
boolean mScalingRequired;
/**
* If set, ViewAncestor doesn't use its lame animation for when the window resizes.
* If set, ViewRootImpl doesn't use its lame animation for when the window resizes.
*/
boolean mTurnOffWindowResizeAnim;

View File

@ -17,7 +17,6 @@
package android.widget;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.SystemClock;
@ -32,14 +31,12 @@ import java.util.Calendar;
/**
* Like AnalogClock, but digital. Shows seconds.
*
* FIXME: implement separate views for hours/minutes/seconds, so
* proportional fonts don't shake rendering
*
* @deprecated It is recommended you use a {@link TextView} and {@link DateFormat}
* to implement the same behavior.
* @deprecated It is recommended you use {@link TextClock} instead.
*/
@Deprecated
public class DigitalClock extends TextView {
// FIXME: implement separate views for hours/minutes/seconds, so
// proportional fonts don't shake rendering
Calendar mCalendar;
private final static String m12 = "h:mm:ss aa";
@ -86,16 +83,16 @@ public class DigitalClock extends TextView {
* requests a tick on the next hard-second boundary
*/
mTicker = new Runnable() {
public void run() {
if (mTickerStopped) return;
mCalendar.setTimeInMillis(System.currentTimeMillis());
setText(DateFormat.format(mFormat, mCalendar));
invalidate();
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
mHandler.postAtTime(mTicker, next);
}
};
public void run() {
if (mTickerStopped) return;
mCalendar.setTimeInMillis(System.currentTimeMillis());
setText(DateFormat.format(mFormat, mCalendar));
invalidate();
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
mHandler.postAtTime(mTicker, next);
}
};
mTicker.run();
}
@ -134,12 +131,14 @@ public class DigitalClock extends TextView {
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
//noinspection deprecation
event.setClassName(DigitalClock.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
//noinspection deprecation
info.setClassName(DigitalClock.class.getName());
}
}

View File

@ -0,0 +1,482 @@
/*
* 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 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.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import com.android.internal.R;
import java.util.Calendar;
import java.util.TimeZone;
import static android.view.ViewDebug.ExportedProperty;
import static android.widget.RemoteViews.*;
/**
* <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.</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 {@link #DEFAULT_FORMAT_24_HOUR}</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 {@link #DEFAULT_FORMAT_12_HOUR}</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.</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 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;
@ExportedProperty
private CharSequence mFormat;
@ExportedProperty
private boolean mHasSeconds;
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) {
if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
final String timeZone = intent.getStringExtra("time-zone");
createTime(timeZone);
}
onTimeChanged();
}
}
};
private final Runnable mTicker = new Runnable() {
public void run() {
onTimeChanged();
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
getHandler().postAtTime(mTicker, next);
}
};
/**
* 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);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextClock, defStyle, 0);
try {
CharSequence format;
format = a.getText(R.styleable.TextClock_format12Hour);
mFormat12 = format == null ? DEFAULT_FORMAT_12_HOUR : format;
format = a.getText(R.styleable.TextClock_format24Hour);
mFormat24 = format == null ? DEFAULT_FORMAT_24_HOUR : format;
mTimeZone = a.getString(R.styleable.TextClock_timeZone);
} finally {
a.recycle();
}
init();
}
private void init() {
createTime(mTimeZone);
// Wait until onAttachedToWindow() to handle the ticker
chooseFormat(false);
}
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()
*/
@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 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 DateFormat}
*
* @see #getFormat12Hour()
* @see #is24HourModeEnabled()
* @see #DEFAULT_FORMAT_12_HOUR
* @see DateFormat
*
* @attr ref android.R.styleable#TextClock_format12Hour
*/
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 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
* {@link 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 DateFormat}
*
* @see #getFormat24Hour()
* @see #is24HourModeEnabled()
* @see #DEFAULT_FORMAT_24_HOUR
* @see DateFormat
*
* @attr ref android.R.styleable#TextClock_format24Hour
*/
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)
*
* @attr ref android.R.styleable#TextClock_timeZone
*/
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.
*
* Calling this method does not schedule or unschedule the time ticker.
*/
private void chooseFormat() {
chooseFormat(true);
}
/**
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
* depending on whether the user has selected 24-hour format.
*
* @param handleTicker true if calling this method should schedule/unschedule the
* time ticker, false otherwise
*/
private void chooseFormat(boolean handleTicker) {
final boolean format24Requested = is24HourModeEnabled();
if (format24Requested) {
mFormat = abc(mFormat24, mFormat12, DEFAULT_FORMAT_24_HOUR);
} else {
mFormat = abc(mFormat12, mFormat24, DEFAULT_FORMAT_12_HOUR);
}
boolean hadSeconds = mHasSeconds;
mHasSeconds = DateFormat.hasSeconds(mFormat);
if (handleTicker) {
if (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 (!mAttached) {
mAttached = true;
registerReceiver();
registerObserver();
createTime(mTimeZone);
if (mHasSeconds) {
mTicker.run();
} else {
onTimeChanged();
}
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAttached) {
unregisterReceiver();
unregisterObserver();
getHandler().removeCallbacks(mTicker);
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));
}
}