1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.deskclock.widget; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.database.ContentObserver; 22 import android.net.Uri; 23 import android.os.Handler; 24 import android.provider.Settings; 25 import androidx.annotation.VisibleForTesting; 26 import android.text.format.DateFormat; 27 import android.util.AttributeSet; 28 import android.widget.TextView; 29 30 import com.android.deskclock.Utils; 31 import com.android.deskclock.data.DataModel; 32 33 import java.util.Calendar; 34 import java.util.TimeZone; 35 36 import static java.util.Calendar.HOUR_OF_DAY; 37 import static java.util.Calendar.MINUTE; 38 39 /** 40 * Based on {@link android.widget.TextClock}, This widget displays a constant time of day using 41 * format specifiers. {@link android.widget.TextClock} doesn't support a non-ticking clock. 42 */ 43 public class TextTime extends TextView { 44 45 /** UTC does not have DST rules and will not alter the {@link #mHour} and {@link #mMinute}. */ 46 private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); 47 48 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 49 static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a"; 50 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 51 static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm"; 52 53 private CharSequence mFormat12; 54 private CharSequence mFormat24; 55 private CharSequence mFormat; 56 57 private boolean mAttached; 58 59 private int mHour; 60 private int mMinute; 61 62 private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { 63 @Override 64 public void onChange(boolean selfChange) { 65 chooseFormat(); 66 updateTime(); 67 } 68 69 @Override 70 public void onChange(boolean selfChange, Uri uri) { 71 chooseFormat(); 72 updateTime(); 73 } 74 }; 75 76 @SuppressWarnings("UnusedDeclaration") TextTime(Context context)77 public TextTime(Context context) { 78 this(context, null); 79 } 80 81 @SuppressWarnings("UnusedDeclaration") TextTime(Context context, AttributeSet attrs)82 public TextTime(Context context, AttributeSet attrs) { 83 this(context, attrs, 0); 84 } 85 TextTime(Context context, AttributeSet attrs, int defStyle)86 public TextTime(Context context, AttributeSet attrs, int defStyle) { 87 super(context, attrs, defStyle); 88 89 setFormat12Hour(Utils.get12ModeFormat(0.3f /* amPmRatio */, false)); 90 setFormat24Hour(Utils.get24ModeFormat(false)); 91 92 chooseFormat(); 93 } 94 95 @SuppressWarnings("UnusedDeclaration") getFormat12Hour()96 public CharSequence getFormat12Hour() { 97 return mFormat12; 98 } 99 100 @SuppressWarnings("UnusedDeclaration") setFormat12Hour(CharSequence format)101 public void setFormat12Hour(CharSequence format) { 102 mFormat12 = format; 103 104 chooseFormat(); 105 updateTime(); 106 } 107 108 @SuppressWarnings("UnusedDeclaration") getFormat24Hour()109 public CharSequence getFormat24Hour() { 110 return mFormat24; 111 } 112 113 @SuppressWarnings("UnusedDeclaration") setFormat24Hour(CharSequence format)114 public void setFormat24Hour(CharSequence format) { 115 mFormat24 = format; 116 117 chooseFormat(); 118 updateTime(); 119 } 120 chooseFormat()121 private void chooseFormat() { 122 final boolean format24Requested = DataModel.getDataModel().is24HourFormat(); 123 if (format24Requested) { 124 mFormat = mFormat24 == null ? DEFAULT_FORMAT_24_HOUR : mFormat24; 125 } else { 126 mFormat = mFormat12 == null ? DEFAULT_FORMAT_12_HOUR : mFormat12; 127 } 128 } 129 130 @Override onAttachedToWindow()131 protected void onAttachedToWindow() { 132 super.onAttachedToWindow(); 133 if (!mAttached) { 134 mAttached = true; 135 registerObserver(); 136 updateTime(); 137 } 138 } 139 140 @Override onDetachedFromWindow()141 protected void onDetachedFromWindow() { 142 super.onDetachedFromWindow(); 143 if (mAttached) { 144 unregisterObserver(); 145 mAttached = false; 146 } 147 } 148 registerObserver()149 private void registerObserver() { 150 final ContentResolver resolver = getContext().getContentResolver(); 151 resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver); 152 } 153 unregisterObserver()154 private void unregisterObserver() { 155 final ContentResolver resolver = getContext().getContentResolver(); 156 resolver.unregisterContentObserver(mFormatChangeObserver); 157 } 158 setTime(int hour, int minute)159 public void setTime(int hour, int minute) { 160 mHour = hour; 161 mMinute = minute; 162 updateTime(); 163 } 164 updateTime()165 private void updateTime() { 166 // Format the time relative to UTC to ensure hour and minute are not adjusted for DST. 167 final Calendar calendar = DataModel.getDataModel().getCalendar(); 168 calendar.setTimeZone(UTC); 169 calendar.set(HOUR_OF_DAY, mHour); 170 calendar.set(MINUTE, mMinute); 171 final CharSequence text = DateFormat.format(mFormat, calendar); 172 setText(text); 173 // Strip away the spans from text so talkback is not confused 174 setContentDescription(text.toString()); 175 } 176 }