1 /* 2 * Copyright (C) 2008 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.internal.widget; 18 19 import com.android.internal.R; 20 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.database.ContentObserver; 26 import android.graphics.Typeface; 27 import android.os.Handler; 28 import android.provider.Settings; 29 import android.text.format.DateFormat; 30 import android.util.AttributeSet; 31 import android.view.View; 32 import android.widget.RelativeLayout; 33 import android.widget.TextView; 34 35 import java.lang.ref.WeakReference; 36 import java.text.DateFormatSymbols; 37 import java.util.Calendar; 38 39 /** 40 * Displays the time 41 */ 42 public class DigitalClock extends RelativeLayout { 43 44 private static final String SYSTEM = "/system/fonts/"; 45 private static final String SYSTEM_FONT_TIME_BACKGROUND = SYSTEM + "AndroidClock.ttf"; 46 private static final String SYSTEM_FONT_TIME_FOREGROUND = SYSTEM + "AndroidClock_Highlight.ttf"; 47 private final static String M12 = "h:mm"; 48 private final static String M24 = "kk:mm"; 49 50 private Calendar mCalendar; 51 private String mFormat; 52 private TextView mTimeDisplayBackground; 53 private TextView mTimeDisplayForeground; 54 private AmPm mAmPm; 55 private ContentObserver mFormatChangeObserver; 56 private int mAttached = 0; // for debugging - tells us whether attach/detach is unbalanced 57 58 /* called by system on minute ticks */ 59 private final Handler mHandler = new Handler(); 60 private BroadcastReceiver mIntentReceiver; 61 62 private static final Typeface sBackgroundFont; 63 private static final Typeface sForegroundFont; 64 65 static { 66 sBackgroundFont = Typeface.createFromFile(SYSTEM_FONT_TIME_BACKGROUND); 67 sForegroundFont = Typeface.createFromFile(SYSTEM_FONT_TIME_FOREGROUND); 68 } 69 70 private static class TimeChangedReceiver extends BroadcastReceiver { 71 private WeakReference<DigitalClock> mClock; 72 private Context mContext; 73 74 public TimeChangedReceiver(DigitalClock clock) { 75 mClock = new WeakReference<DigitalClock>(clock); 76 mContext = clock.getContext(); 77 } 78 79 @Override 80 public void onReceive(Context context, Intent intent) { 81 // Post a runnable to avoid blocking the broadcast. 82 final boolean timezoneChanged = 83 intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED); 84 final DigitalClock clock = mClock.get(); 85 if (clock != null) { 86 clock.mHandler.post(new Runnable() { 87 public void run() { 88 if (timezoneChanged) { 89 clock.mCalendar = Calendar.getInstance(); 90 } 91 clock.updateTime(); 92 } 93 }); 94 } else { 95 try { 96 mContext.unregisterReceiver(this); 97 } catch (RuntimeException e) { 98 // Shouldn't happen 99 } 100 } 101 } 102 }; 103 104 static class AmPm { 105 private TextView mAmPmTextView; 106 private String mAmString, mPmString; 107 108 AmPm(View parent, Typeface tf) { 109 // No longer used, uncomment if we decide to use AM/PM indicator again 110 // mAmPmTextView = (TextView) parent.findViewById(R.id.am_pm); 111 if (mAmPmTextView != null && tf != null) { 112 mAmPmTextView.setTypeface(tf); 113 } 114 115 String[] ampm = new DateFormatSymbols().getAmPmStrings(); 116 mAmString = ampm[0]; 117 mPmString = ampm[1]; 118 } 119 120 void setShowAmPm(boolean show) { 121 if (mAmPmTextView != null) { 122 mAmPmTextView.setVisibility(show ? View.VISIBLE : View.GONE); 123 } 124 } 125 126 void setIsMorning(boolean isMorning) { 127 if (mAmPmTextView != null) { 128 mAmPmTextView.setText(isMorning ? mAmString : mPmString); 129 } 130 } 131 } 132 133 private static class FormatChangeObserver extends ContentObserver { 134 private WeakReference<DigitalClock> mClock; 135 private Context mContext; 136 public FormatChangeObserver(DigitalClock clock) { 137 super(new Handler()); 138 mClock = new WeakReference<DigitalClock>(clock); 139 mContext = clock.getContext(); 140 } 141 @Override 142 public void onChange(boolean selfChange) { 143 DigitalClock digitalClock = mClock.get(); 144 if (digitalClock != null) { 145 digitalClock.setDateFormat(); 146 digitalClock.updateTime(); 147 } else { 148 try { 149 mContext.getContentResolver().unregisterContentObserver(this); 150 } catch (RuntimeException e) { 151 // Shouldn't happen 152 } 153 } 154 } 155 } 156 157 public DigitalClock(Context context) { 158 this(context, null); 159 } 160 161 public DigitalClock(Context context, AttributeSet attrs) { 162 super(context, attrs); 163 } 164 165 @Override 166 protected void onFinishInflate() { 167 super.onFinishInflate(); 168 169 /* The time display consists of two tones. That's why we have two overlapping text views. */ 170 mTimeDisplayBackground = (TextView) findViewById(R.id.timeDisplayBackground); 171 mTimeDisplayBackground.setTypeface(sBackgroundFont); 172 mTimeDisplayBackground.setVisibility(View.INVISIBLE); 173 174 mTimeDisplayForeground = (TextView) findViewById(R.id.timeDisplayForeground); 175 mTimeDisplayForeground.setTypeface(sForegroundFont); 176 mAmPm = new AmPm(this, null); 177 mCalendar = Calendar.getInstance(); 178 179 setDateFormat(); 180 } 181 182 @Override 183 protected void onAttachedToWindow() { 184 super.onAttachedToWindow(); 185 186 mAttached++; 187 188 /* monitor time ticks, time changed, timezone */ 189 if (mIntentReceiver == null) { 190 mIntentReceiver = new TimeChangedReceiver(this); 191 IntentFilter filter = new IntentFilter(); 192 filter.addAction(Intent.ACTION_TIME_TICK); 193 filter.addAction(Intent.ACTION_TIME_CHANGED); 194 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 195 mContext.registerReceiver(mIntentReceiver, filter); 196 } 197 198 /* monitor 12/24-hour display preference */ 199 if (mFormatChangeObserver == null) { 200 mFormatChangeObserver = new FormatChangeObserver(this); 201 mContext.getContentResolver().registerContentObserver( 202 Settings.System.CONTENT_URI, true, mFormatChangeObserver); 203 } 204 205 updateTime(); 206 } 207 208 @Override 209 protected void onDetachedFromWindow() { 210 super.onDetachedFromWindow(); 211 212 mAttached--; 213 214 if (mIntentReceiver != null) { 215 mContext.unregisterReceiver(mIntentReceiver); 216 } 217 if (mFormatChangeObserver != null) { 218 mContext.getContentResolver().unregisterContentObserver( 219 mFormatChangeObserver); 220 } 221 222 mFormatChangeObserver = null; 223 mIntentReceiver = null; 224 } 225 226 void updateTime(Calendar c) { 227 mCalendar = c; 228 updateTime(); 229 } 230 231 public void updateTime() { 232 mCalendar.setTimeInMillis(System.currentTimeMillis()); 233 234 CharSequence newTime = DateFormat.format(mFormat, mCalendar); 235 mTimeDisplayBackground.setText(newTime); 236 mTimeDisplayForeground.setText(newTime); 237 mAmPm.setIsMorning(mCalendar.get(Calendar.AM_PM) == 0); 238 } 239 240 private void setDateFormat() { 241 mFormat = android.text.format.DateFormat.is24HourFormat(getContext()) 242 ? M24 : M12; 243 mAmPm.setShowAmPm(mFormat.equals(M12)); 244 } 245 } 246