OSDN Git Service

A new clock widget to create lock screen appwidgets
authorRomain Guy <romainguy@google.com>
Thu, 1 Nov 2012 03:31:58 +0000 (20:31 -0700)
committerRomain Guy <romainguy@google.com>
Thu, 1 Nov 2012 03:31:58 +0000 (20:31 -0700)
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

api/17.txt
api/current.txt
core/java/android/text/format/DateFormat.java
core/java/android/view/View.java
core/java/android/widget/DigitalClock.java
core/java/android/widget/TextClock.java [new file with mode: 0644]
core/res/res/values/attrs.xml
core/res/res/values/public.xml

index e26d8f0..fde6302 100644 (file)
@@ -491,6 +491,8 @@ package android {
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
     field public static final int format = 16843013; // 0x1010105
+    field public static final int format12Hour = 16843722; // 0x10103ca
+    field public static final int format24Hour = 16843723; // 0x10103cb
     field public static final int fragment = 16843491; // 0x10102e3
     field public static final int fragmentCloseEnterAnimation = 16843495; // 0x10102e7
     field public static final int fragmentCloseExitAnimation = 16843496; // 0x10102e8
@@ -1074,6 +1076,7 @@ package android {
     field public static final int thumbTextPadding = 16843634; // 0x1010372
     field public static final int thumbnail = 16843429; // 0x10102a5
     field public static final int tileMode = 16843265; // 0x1010201
+    field public static final int timeZone = 16843724; // 0x10103cc
     field public static final int tint = 16843041; // 0x1010121
     field public static final int title = 16843233; // 0x10101e1
     field public static final int titleCondensed = 16843234; // 0x10101e2
@@ -22300,14 +22303,14 @@ package android.text.format {
 
   public class DateFormat {
     ctor public DateFormat();
-    method public static final java.lang.CharSequence format(java.lang.CharSequence, long);
-    method public static final java.lang.CharSequence format(java.lang.CharSequence, java.util.Date);
-    method public static final java.lang.CharSequence format(java.lang.CharSequence, java.util.Calendar);
-    method public static final java.text.DateFormat getDateFormat(android.content.Context);
-    method public static final char[] getDateFormatOrder(android.content.Context);
-    method public static final java.text.DateFormat getLongDateFormat(android.content.Context);
-    method public static final java.text.DateFormat getMediumDateFormat(android.content.Context);
-    method public static final java.text.DateFormat getTimeFormat(android.content.Context);
+    method public static java.lang.CharSequence format(java.lang.CharSequence, long);
+    method public static java.lang.CharSequence format(java.lang.CharSequence, java.util.Date);
+    method public static java.lang.CharSequence format(java.lang.CharSequence, java.util.Calendar);
+    method public static java.text.DateFormat getDateFormat(android.content.Context);
+    method public static char[] getDateFormatOrder(android.content.Context);
+    method public static java.text.DateFormat getLongDateFormat(android.content.Context);
+    method public static java.text.DateFormat getMediumDateFormat(android.content.Context);
+    method public static java.text.DateFormat getTimeFormat(android.content.Context);
     method public static boolean is24HourFormat(android.content.Context);
     field public static final char AM_PM = 97; // 0x0061 'a'
     field public static final char CAPITAL_AM_PM = 65; // 0x0041 'A'
@@ -29518,6 +29521,21 @@ package android.widget {
     field public int span;
   }
 
+  public class TextClock extends android.widget.TextView {
+    ctor public TextClock(android.content.Context);
+    ctor public TextClock(android.content.Context, android.util.AttributeSet);
+    ctor public TextClock(android.content.Context, android.util.AttributeSet, int);
+    method public java.lang.CharSequence getFormat12Hour();
+    method public java.lang.CharSequence getFormat24Hour();
+    method public java.lang.String getTimeZone();
+    method public boolean is24HourModeEnabled();
+    method public void setFormat12Hour(java.lang.CharSequence);
+    method public void setFormat24Hour(java.lang.CharSequence);
+    method public void setTimeZone(java.lang.String);
+    field public static final java.lang.CharSequence DEFAULT_FORMAT_12_HOUR;
+    field public static final java.lang.CharSequence DEFAULT_FORMAT_24_HOUR;
+  }
+
   public class TextSwitcher extends android.widget.ViewSwitcher {
     ctor public TextSwitcher(android.content.Context);
     ctor public TextSwitcher(android.content.Context, android.util.AttributeSet);
index e26d8f0..fde6302 100644 (file)
@@ -491,6 +491,8 @@ package android {
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
     field public static final int format = 16843013; // 0x1010105
+    field public static final int format12Hour = 16843722; // 0x10103ca
+    field public static final int format24Hour = 16843723; // 0x10103cb
     field public static final int fragment = 16843491; // 0x10102e3
     field public static final int fragmentCloseEnterAnimation = 16843495; // 0x10102e7
     field public static final int fragmentCloseExitAnimation = 16843496; // 0x10102e8
@@ -1074,6 +1076,7 @@ package android {
     field public static final int thumbTextPadding = 16843634; // 0x1010372
     field public static final int thumbnail = 16843429; // 0x10102a5
     field public static final int tileMode = 16843265; // 0x1010201
+    field public static final int timeZone = 16843724; // 0x10103cc
     field public static final int tint = 16843041; // 0x1010121
     field public static final int title = 16843233; // 0x10101e1
     field public static final int titleCondensed = 16843234; // 0x10101e2
@@ -22300,14 +22303,14 @@ package android.text.format {
 
   public class DateFormat {
     ctor public DateFormat();
-    method public static final java.lang.CharSequence format(java.lang.CharSequence, long);
-    method public static final java.lang.CharSequence format(java.lang.CharSequence, java.util.Date);
-    method public static final java.lang.CharSequence format(java.lang.CharSequence, java.util.Calendar);
-    method public static final java.text.DateFormat getDateFormat(android.content.Context);
-    method public static final char[] getDateFormatOrder(android.content.Context);
-    method public static final java.text.DateFormat getLongDateFormat(android.content.Context);
-    method public static final java.text.DateFormat getMediumDateFormat(android.content.Context);
-    method public static final java.text.DateFormat getTimeFormat(android.content.Context);
+    method public static java.lang.CharSequence format(java.lang.CharSequence, long);
+    method public static java.lang.CharSequence format(java.lang.CharSequence, java.util.Date);
+    method public static java.lang.CharSequence format(java.lang.CharSequence, java.util.Calendar);
+    method public static java.text.DateFormat getDateFormat(android.content.Context);
+    method public static char[] getDateFormatOrder(android.content.Context);
+    method public static java.text.DateFormat getLongDateFormat(android.content.Context);
+    method public static java.text.DateFormat getMediumDateFormat(android.content.Context);
+    method public static java.text.DateFormat getTimeFormat(android.content.Context);
     method public static boolean is24HourFormat(android.content.Context);
     field public static final char AM_PM = 97; // 0x0061 'a'
     field public static final char CAPITAL_AM_PM = 65; // 0x0041 'A'
@@ -29518,6 +29521,21 @@ package android.widget {
     field public int span;
   }
 
+  public class TextClock extends android.widget.TextView {
+    ctor public TextClock(android.content.Context);
+    ctor public TextClock(android.content.Context, android.util.AttributeSet);
+    ctor public TextClock(android.content.Context, android.util.AttributeSet, int);
+    method public java.lang.CharSequence getFormat12Hour();
+    method public java.lang.CharSequence getFormat24Hour();
+    method public java.lang.String getTimeZone();
+    method public boolean is24HourModeEnabled();
+    method public void setFormat12Hour(java.lang.CharSequence);
+    method public void setFormat24Hour(java.lang.CharSequence);
+    method public void setTimeZone(java.lang.String);
+    field public static final java.lang.CharSequence DEFAULT_FORMAT_12_HOUR;
+    field public static final java.lang.CharSequence DEFAULT_FORMAT_24_HOUR;
+  }
+
   public class TextSwitcher extends android.widget.ViewSwitcher {
     ctor public TextSwitcher(android.content.Context);
     ctor public TextSwitcher(android.content.Context, android.util.AttributeSet);
index c36273e..3c984b5 100644 (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);
@@ -440,13 +441,75 @@ public class DateFormat {
     }
 
     /**
+     * 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.
      * @param inFormat the format string, as described in {@link android.text.format.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);
     }
 }
index 9d0d4f0..d5e1ed3 100644 (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;
 
index 3e9107f..c6b6dd6 100644 (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());
     }
 }
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
new file mode 100644 (file)
index 0000000..4c46658
--- /dev/null
@@ -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));
+    }
+}
index d186c4a..6505ad0 100755 (executable)
         <!-- Present the text in ALL CAPS. This may use a small-caps form when available. -->
         <attr name="textAllCaps" format="boolean" />
     </declare-styleable>
+    <declare-styleable name="TextClock">
+        <!-- Specifies the formatting pattern used to show the time and/or date
+             in 12-hour mode. Please refer to {@link android.text.format.DateFormat}
+             for a complete description of accepted formatting patterns.
+             The default pattern is "h:mm aa". -->
+        <attr name="format12Hour" format="string"/>
+        <!-- Specifies the formatting pattern used to show the time and/or date
+             in 24-hour mode. Please refer to {@link android.text.format.DateFormat}
+             for a complete description of accepted formatting patterns.
+             The default pattern is "k:mm". -->
+        <attr name="format24Hour" format="string"/>
+        <!-- Specifies the time zone to use. When this attribute is specified, the
+             TextClock will ignore the time zone of the system. To use the user's
+             time zone, do not specify this attribute. The default value is the
+             user's time zone. Please refer to {@link java.util.TimeZone} for more
+             information about time zone ids. -->
+        <attr name="timeZone" format="string"/>
+    </declare-styleable>
     <declare-styleable name="TextSwitcher">
     </declare-styleable>
     <declare-styleable name="TextView">
index d7a480b..06d3110 100644 (file)
   <public type="attr" name="permissionFlags" id="0x010103c7" />
   <public type="attr" name="checkedTextViewStyle" id="0x010103c8" />
   <public type="attr" name="showOnLockScreen" id="0x010103c9" />
+  <public type="attr" name="format12Hour" id="0x010103ca" />
+  <public type="attr" name="format24Hour" id="0x010103cb" />
+  <public type="attr" name="timeZone" id="0x010103cc" />
 
   <public type="style" name="Widget.Holo.CheckedTextView" id="0x010301d9" />
   <public type="style" name="Widget.Holo.Light.CheckedTextView" id="0x010301da" />