1 /* 2 * Copyright (C) 2006 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.systemui.statusbar.policy; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.res.TypedArray; 24 import android.os.Bundle; 25 import android.text.Spannable; 26 import android.text.SpannableStringBuilder; 27 import android.text.format.DateFormat; 28 import android.text.style.CharacterStyle; 29 import android.text.style.RelativeSizeSpan; 30 import android.util.AttributeSet; 31 import android.widget.TextView; 32 33 import com.android.systemui.DemoMode; 34 import com.android.systemui.R; 35 36 import java.text.SimpleDateFormat; 37 import java.util.Calendar; 38 import java.util.Locale; 39 import java.util.TimeZone; 40 41 import libcore.icu.LocaleData; 42 43 /** 44 * Digital clock for the status bar. 45 */ 46 public class Clock extends TextView implements DemoMode { 47 private boolean mAttached; 48 private Calendar mCalendar; 49 private String mClockFormatString; 50 private SimpleDateFormat mClockFormat; 51 private Locale mLocale; 52 53 private static final int AM_PM_STYLE_NORMAL = 0; 54 private static final int AM_PM_STYLE_SMALL = 1; 55 private static final int AM_PM_STYLE_GONE = 2; 56 57 private final int mAmPmStyle; 58 59 public Clock(Context context) { 60 this(context, null); 61 } 62 63 public Clock(Context context, AttributeSet attrs) { 64 this(context, attrs, 0); 65 } 66 67 public Clock(Context context, AttributeSet attrs, int defStyle) { 68 super(context, attrs, defStyle); 69 TypedArray a = context.getTheme().obtainStyledAttributes( 70 attrs, 71 R.styleable.Clock, 72 0, 0); 73 try { 74 mAmPmStyle = a.getInt(R.styleable.Clock_amPmStyle, AM_PM_STYLE_GONE); 75 } finally { 76 a.recycle(); 77 } 78 } 79 80 @Override 81 protected void onAttachedToWindow() { 82 super.onAttachedToWindow(); 83 84 if (!mAttached) { 85 mAttached = true; 86 IntentFilter filter = new IntentFilter(); 87 88 filter.addAction(Intent.ACTION_TIME_TICK); 89 filter.addAction(Intent.ACTION_TIME_CHANGED); 90 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 91 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 92 filter.addAction(Intent.ACTION_USER_SWITCHED); 93 94 getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); 95 } 96 97 // NOTE: It's safe to do these after registering the receiver since the receiver always runs 98 // in the main thread, therefore the receiver can't run before this method returns. 99 100 // The time zone may have changed while the receiver wasn't registered, so update the Time 101 mCalendar = Calendar.getInstance(TimeZone.getDefault()); 102 103 // Make sure we update to the current time 104 updateClock(); 105 } 106 107 @Override 108 protected void onDetachedFromWindow() { 109 super.onDetachedFromWindow(); 110 if (mAttached) { 111 getContext().unregisterReceiver(mIntentReceiver); 112 mAttached = false; 113 } 114 } 115 116 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 117 @Override 118 public void onReceive(Context context, Intent intent) { 119 String action = intent.getAction(); 120 if (action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { 121 String tz = intent.getStringExtra("time-zone"); 122 mCalendar = Calendar.getInstance(TimeZone.getTimeZone(tz)); 123 if (mClockFormat != null) { 124 mClockFormat.setTimeZone(mCalendar.getTimeZone()); 125 } 126 } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { 127 final Locale newLocale = getResources().getConfiguration().locale; 128 if (! newLocale.equals(mLocale)) { 129 mLocale = newLocale; 130 mClockFormatString = ""; // force refresh 131 } 132 } 133 updateClock(); 134 } 135 }; 136 137 final void updateClock() { 138 if (mDemoMode) return; 139 mCalendar.setTimeInMillis(System.currentTimeMillis()); 140 setText(getSmallTime()); 141 } 142 143 private final CharSequence getSmallTime() { 144 Context context = getContext(); 145 boolean is24 = DateFormat.is24HourFormat(context); 146 LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale); 147 148 final char MAGIC1 = '\uEF00'; 149 final char MAGIC2 = '\uEF01'; 150 151 SimpleDateFormat sdf; 152 String format = is24 ? d.timeFormat24 : d.timeFormat12; 153 if (!format.equals(mClockFormatString)) { 154 /* 155 * Search for an unquoted "a" in the format string, so we can 156 * add dummy characters around it to let us find it again after 157 * formatting and change its size. 158 */ 159 if (mAmPmStyle != AM_PM_STYLE_NORMAL) { 160 int a = -1; 161 boolean quoted = false; 162 for (int i = 0; i < format.length(); i++) { 163 char c = format.charAt(i); 164 165 if (c == '\'') { 166 quoted = !quoted; 167 } 168 if (!quoted && c == 'a') { 169 a = i; 170 break; 171 } 172 } 173 174 if (a >= 0) { 175 // Move a back so any whitespace before AM/PM is also in the alternate size. 176 final int b = a; 177 while (a > 0 && Character.isWhitespace(format.charAt(a-1))) { 178 a--; 179 } 180 format = format.substring(0, a) + MAGIC1 + format.substring(a, b) 181 + "a" + MAGIC2 + format.substring(b + 1); 182 } 183 } 184 mClockFormat = sdf = new SimpleDateFormat(format); 185 mClockFormatString = format; 186 } else { 187 sdf = mClockFormat; 188 } 189 String result = sdf.format(mCalendar.getTime()); 190 191 if (mAmPmStyle != AM_PM_STYLE_NORMAL) { 192 int magic1 = result.indexOf(MAGIC1); 193 int magic2 = result.indexOf(MAGIC2); 194 if (magic1 >= 0 && magic2 > magic1) { 195 SpannableStringBuilder formatted = new SpannableStringBuilder(result); 196 if (mAmPmStyle == AM_PM_STYLE_GONE) { 197 formatted.delete(magic1, magic2+1); 198 } else { 199 if (mAmPmStyle == AM_PM_STYLE_SMALL) { 200 CharacterStyle style = new RelativeSizeSpan(0.7f); 201 formatted.setSpan(style, magic1, magic2, 202 Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 203 } 204 formatted.delete(magic2, magic2 + 1); 205 formatted.delete(magic1, magic1 + 1); 206 } 207 return formatted; 208 } 209 } 210 211 return result; 212 213 } 214 215 private boolean mDemoMode; 216 217 @Override 218 public void dispatchDemoCommand(String command, Bundle args) { 219 if (!mDemoMode && command.equals(COMMAND_ENTER)) { 220 mDemoMode = true; 221 } else if (mDemoMode && command.equals(COMMAND_EXIT)) { 222 mDemoMode = false; 223 updateClock(); 224 } else if (mDemoMode && command.equals(COMMAND_CLOCK)) { 225 String millis = args.getString("millis"); 226 String hhmm = args.getString("hhmm"); 227 if (millis != null) { 228 mCalendar.setTimeInMillis(Long.parseLong(millis)); 229 } else if (hhmm != null && hhmm.length() == 4) { 230 int hh = Integer.parseInt(hhmm.substring(0, 2)); 231 int mm = Integer.parseInt(hhmm.substring(2)); 232 mCalendar.set(Calendar.HOUR, hh); 233 mCalendar.set(Calendar.MINUTE, mm); 234 } 235 setText(getSmallTime()); 236 } 237 } 238 } 239 240