1be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux/* 2be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * Copyright (C) 2016 The Android Open Source Project 3be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * 4be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * Licensed under the Apache License, Version 2.0 (the "License"); 5be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * you may not use this file except in compliance with the License. 6be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * You may obtain a copy of the License at 7be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * 8be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * http://www.apache.org/licenses/LICENSE-2.0 9be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * 10be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * Unless required by applicable law or agreed to in writing, software 11be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * distributed under the License is distributed on an "AS IS" BASIS, 12be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * See the License for the specific language governing permissions and 14be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * limitations under the License. 15be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux */ 161f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 171f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertpackage com.android.deskclock.widget; 181f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 191f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertimport android.content.ContentResolver; 201f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertimport android.content.Context; 211f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertimport android.database.ContentObserver; 221f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertimport android.net.Uri; 231f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertimport android.os.Handler; 241f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertimport android.provider.Settings; 250b19985ecfa0f43acb8ededc173f643843098a95James Lemieuximport android.support.annotation.VisibleForTesting; 261f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertimport android.text.format.DateFormat; 271f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertimport android.util.AttributeSet; 281f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertimport android.widget.TextView; 291f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 303a4ba0db218b830af3dd17fde2952125a2e50fdcIsaac Katzenelsonimport com.android.deskclock.Utils; 310b19985ecfa0f43acb8ededc173f643843098a95James Lemieuximport com.android.deskclock.data.DataModel; 321f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 331f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertimport java.util.Calendar; 340b19985ecfa0f43acb8ededc173f643843098a95James Lemieuximport java.util.TimeZone; 350b19985ecfa0f43acb8ededc173f643843098a95James Lemieux 360b19985ecfa0f43acb8ededc173f643843098a95James Lemieuximport static java.util.Calendar.HOUR_OF_DAY; 370b19985ecfa0f43acb8ededc173f643843098a95James Lemieuximport static java.util.Calendar.MINUTE; 381f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 391f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert/** 401f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert * Based on {@link android.widget.TextClock}, This widget displays a constant time of day using 41be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux * format specifiers. {@link android.widget.TextClock} doesn't support a non-ticking clock. 421f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert */ 431f33a02b053c1de549f2d3bf23df93d83defac98Alon Albertpublic class TextTime extends TextView { 441f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 450b19985ecfa0f43acb8ededc173f643843098a95James Lemieux /** UTC does not have DST rules and will not alter the {@link #mHour} and {@link #mMinute}. */ 460b19985ecfa0f43acb8ededc173f643843098a95James Lemieux private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); 470b19985ecfa0f43acb8ededc173f643843098a95James Lemieux 480b19985ecfa0f43acb8ededc173f643843098a95James Lemieux @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 490b19985ecfa0f43acb8ededc173f643843098a95James Lemieux static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a"; 500b19985ecfa0f43acb8ededc173f643843098a95James Lemieux @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 510b19985ecfa0f43acb8ededc173f643843098a95James Lemieux static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm"; 521f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 531f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private CharSequence mFormat12; 541f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private CharSequence mFormat24; 551f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private CharSequence mFormat; 561f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 571f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private boolean mAttached; 581f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 591f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private int mHour; 601f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private int mMinute; 611f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 621f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { 631f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert @Override 641f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert public void onChange(boolean selfChange) { 651f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert chooseFormat(); 661f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert updateTime(); 671f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 681f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 691f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert @Override 701f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert public void onChange(boolean selfChange, Uri uri) { 711f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert chooseFormat(); 721f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert updateTime(); 731f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 741f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert }; 751f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 761f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert @SuppressWarnings("UnusedDeclaration") 771f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert public TextTime(Context context) { 781f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert this(context, null); 791f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 801f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 811f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert @SuppressWarnings("UnusedDeclaration") 821f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert public TextTime(Context context, AttributeSet attrs) { 831f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert this(context, attrs, 0); 841f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 851f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 861f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert public TextTime(Context context, AttributeSet attrs, int defStyle) { 871f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert super(context, attrs, defStyle); 881f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 897b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks setFormat12Hour(Utils.get12ModeFormat(0.3f /* amPmRatio */, false)); 907b852b43db6d9e50814dd07366b025b3f9f9b758Christine Franks setFormat24Hour(Utils.get24ModeFormat(false)); 91be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux 921f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert chooseFormat(); 931f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 941f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 951f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert @SuppressWarnings("UnusedDeclaration") 961f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert public CharSequence getFormat12Hour() { 971f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert return mFormat12; 981f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 991f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1001f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert @SuppressWarnings("UnusedDeclaration") 1011f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert public void setFormat12Hour(CharSequence format) { 1021f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert mFormat12 = format; 1031f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1041f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert chooseFormat(); 1051f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert updateTime(); 1061f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1071f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1081f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert @SuppressWarnings("UnusedDeclaration") 1091f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert public CharSequence getFormat24Hour() { 1101f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert return mFormat24; 1111f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1121f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1131f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert @SuppressWarnings("UnusedDeclaration") 1141f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert public void setFormat24Hour(CharSequence format) { 1151f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert mFormat24 = format; 1161f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1171f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert chooseFormat(); 1181f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert updateTime(); 1191f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1201f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1211f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private void chooseFormat() { 1220b19985ecfa0f43acb8ededc173f643843098a95James Lemieux final boolean format24Requested = DataModel.getDataModel().is24HourFormat(); 1231f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert if (format24Requested) { 1241f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert mFormat = mFormat24 == null ? DEFAULT_FORMAT_24_HOUR : mFormat24; 1251f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } else { 1261f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert mFormat = mFormat12 == null ? DEFAULT_FORMAT_12_HOUR : mFormat12; 1271f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1281f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1291f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1301f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert @Override 1311f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert protected void onAttachedToWindow() { 1321f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert super.onAttachedToWindow(); 1331f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert if (!mAttached) { 1341f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert mAttached = true; 1351f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert registerObserver(); 1361f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert updateTime(); 1371f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1381f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1391f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1401f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert @Override 1411f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert protected void onDetachedFromWindow() { 1421f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert super.onDetachedFromWindow(); 1431f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert if (mAttached) { 1441f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert unregisterObserver(); 1451f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert mAttached = false; 1461f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1471f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1481f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1491f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private void registerObserver() { 1501f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert final ContentResolver resolver = getContext().getContentResolver(); 1511f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver); 1521f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1531f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1541f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private void unregisterObserver() { 1551f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert final ContentResolver resolver = getContext().getContentResolver(); 1561f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert resolver.unregisterContentObserver(mFormatChangeObserver); 1571f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1581f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1591f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert public void setTime(int hour, int minute) { 1601f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert mHour = hour; 1611f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert mMinute = minute; 1621f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert updateTime(); 1631f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 1641f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert 1651f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert private void updateTime() { 1660b19985ecfa0f43acb8ededc173f643843098a95James Lemieux // Format the time relative to UTC to ensure hour and minute are not adjusted for DST. 1670b19985ecfa0f43acb8ededc173f643843098a95James Lemieux final Calendar calendar = DataModel.getDataModel().getCalendar(); 1680b19985ecfa0f43acb8ededc173f643843098a95James Lemieux calendar.setTimeZone(UTC); 1690b19985ecfa0f43acb8ededc173f643843098a95James Lemieux calendar.set(HOUR_OF_DAY, mHour); 1700b19985ecfa0f43acb8ededc173f643843098a95James Lemieux calendar.set(MINUTE, mMinute); 171d8c0f43ee8f44fab34402ed27a2dcc74f5d1fb76Sean Stout final CharSequence text = DateFormat.format(mFormat, calendar); 172d8c0f43ee8f44fab34402ed27a2dcc74f5d1fb76Sean Stout setText(text); 173d8c0f43ee8f44fab34402ed27a2dcc74f5d1fb76Sean Stout // Strip away the spans from text so talkback is not confused 174d8c0f43ee8f44fab34402ed27a2dcc74f5d1fb76Sean Stout setContentDescription(text.toString()); 1751f33a02b053c1de549f2d3bf23df93d83defac98Alon Albert } 176be4385190b52cd2b3e682d8b81faecfd5d3b2d78James Lemieux}