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