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