1/* 2 * Copyright (C) 2012 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.keyguard; 18 19import android.app.ActivityManager; 20import android.app.AlarmManager; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.content.res.Configuration; 24import android.content.res.Resources; 25import android.os.UserHandle; 26import android.text.TextUtils; 27import android.text.format.DateFormat; 28import android.util.AttributeSet; 29import android.util.Log; 30import android.util.Slog; 31import android.util.TypedValue; 32import android.view.View; 33import android.widget.GridLayout; 34import android.widget.TextClock; 35import android.widget.TextView; 36 37import com.android.internal.widget.LockPatternUtils; 38 39import java.util.Locale; 40 41public class KeyguardStatusView extends GridLayout { 42 private static final boolean DEBUG = KeyguardConstants.DEBUG; 43 private static final String TAG = "KeyguardStatusView"; 44 45 private final LockPatternUtils mLockPatternUtils; 46 private final AlarmManager mAlarmManager; 47 48 private TextView mAlarmStatusView; 49 private TextClock mDateView; 50 private TextClock mClockView; 51 private TextView mOwnerInfo; 52 53 private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { 54 55 @Override 56 public void onTimeChanged() { 57 refresh(); 58 } 59 60 @Override 61 public void onKeyguardVisibilityChanged(boolean showing) { 62 if (showing) { 63 if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing); 64 refresh(); 65 updateOwnerInfo(); 66 } 67 } 68 69 @Override 70 public void onStartedWakingUp() { 71 setEnableMarquee(true); 72 } 73 74 @Override 75 public void onFinishedGoingToSleep(int why) { 76 setEnableMarquee(false); 77 } 78 79 @Override 80 public void onUserSwitchComplete(int userId) { 81 refresh(); 82 updateOwnerInfo(); 83 } 84 }; 85 86 public KeyguardStatusView(Context context) { 87 this(context, null, 0); 88 } 89 90 public KeyguardStatusView(Context context, AttributeSet attrs) { 91 this(context, attrs, 0); 92 } 93 94 public KeyguardStatusView(Context context, AttributeSet attrs, int defStyle) { 95 super(context, attrs, defStyle); 96 mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 97 mLockPatternUtils = new LockPatternUtils(getContext()); 98 } 99 100 private void setEnableMarquee(boolean enabled) { 101 if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee"); 102 if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled); 103 if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled); 104 } 105 106 @Override 107 protected void onFinishInflate() { 108 super.onFinishInflate(); 109 mAlarmStatusView = (TextView) findViewById(R.id.alarm_status); 110 mDateView = (TextClock) findViewById(R.id.date_view); 111 mClockView = (TextClock) findViewById(R.id.clock_view); 112 mDateView.setShowCurrentUserTime(true); 113 mClockView.setShowCurrentUserTime(true); 114 mOwnerInfo = (TextView) findViewById(R.id.owner_info); 115 116 boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); 117 setEnableMarquee(shouldMarquee); 118 refresh(); 119 updateOwnerInfo(); 120 121 // Disable elegant text height because our fancy colon makes the ymin value huge for no 122 // reason. 123 mClockView.setElegantTextHeight(false); 124 } 125 126 @Override 127 protected void onConfigurationChanged(Configuration newConfig) { 128 super.onConfigurationChanged(newConfig); 129 mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 130 getResources().getDimensionPixelSize(R.dimen.widget_big_font_size)); 131 mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 132 getResources().getDimensionPixelSize(R.dimen.widget_label_font_size)); 133 mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX, 134 getResources().getDimensionPixelSize(R.dimen.widget_label_font_size)); 135 } 136 137 public void refreshTime() { 138 mDateView.setFormat24Hour(Patterns.dateView); 139 mDateView.setFormat12Hour(Patterns.dateView); 140 141 mClockView.setFormat12Hour(Patterns.clockView12); 142 mClockView.setFormat24Hour(Patterns.clockView24); 143 } 144 145 private void refresh() { 146 AlarmManager.AlarmClockInfo nextAlarm = 147 mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT); 148 Patterns.update(mContext, nextAlarm != null); 149 150 refreshTime(); 151 refreshAlarmStatus(nextAlarm); 152 } 153 154 void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) { 155 if (nextAlarm != null) { 156 String alarm = formatNextAlarm(mContext, nextAlarm); 157 mAlarmStatusView.setText(alarm); 158 mAlarmStatusView.setContentDescription( 159 getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm)); 160 mAlarmStatusView.setVisibility(View.VISIBLE); 161 } else { 162 mAlarmStatusView.setVisibility(View.GONE); 163 } 164 } 165 166 public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) { 167 if (info == null) { 168 return ""; 169 } 170 String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser()) 171 ? "EHm" 172 : "Ehma"; 173 String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 174 return DateFormat.format(pattern, info.getTriggerTime()).toString(); 175 } 176 177 private void updateOwnerInfo() { 178 if (mOwnerInfo == null) return; 179 String ownerInfo = getOwnerInfo(); 180 if (!TextUtils.isEmpty(ownerInfo)) { 181 mOwnerInfo.setVisibility(View.VISIBLE); 182 mOwnerInfo.setText(ownerInfo); 183 } else { 184 mOwnerInfo.setVisibility(View.GONE); 185 } 186 } 187 188 @Override 189 protected void onAttachedToWindow() { 190 super.onAttachedToWindow(); 191 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mInfoCallback); 192 } 193 194 @Override 195 protected void onDetachedFromWindow() { 196 super.onDetachedFromWindow(); 197 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mInfoCallback); 198 } 199 200 private String getOwnerInfo() { 201 ContentResolver res = getContext().getContentResolver(); 202 String info = null; 203 final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled( 204 KeyguardUpdateMonitor.getCurrentUser()); 205 if (ownerInfoEnabled) { 206 info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); 207 } 208 return info; 209 } 210 211 @Override 212 public boolean hasOverlappingRendering() { 213 return false; 214 } 215 216 // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. 217 // This is an optimization to ensure we only recompute the patterns when the inputs change. 218 private static final class Patterns { 219 static String dateView; 220 static String clockView12; 221 static String clockView24; 222 static String cacheKey; 223 224 static void update(Context context, boolean hasAlarm) { 225 final Locale locale = Locale.getDefault(); 226 final Resources res = context.getResources(); 227 final String dateViewSkel = res.getString(hasAlarm 228 ? R.string.abbrev_wday_month_day_no_year_alarm 229 : R.string.abbrev_wday_month_day_no_year); 230 final String clockView12Skel = res.getString(R.string.clock_12hr_format); 231 final String clockView24Skel = res.getString(R.string.clock_24hr_format); 232 final String key = locale.toString() + dateViewSkel + clockView12Skel + clockView24Skel; 233 if (key.equals(cacheKey)) return; 234 235 dateView = DateFormat.getBestDateTimePattern(locale, dateViewSkel); 236 237 clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel); 238 // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton 239 // format. The following code removes the AM/PM indicator if we didn't want it. 240 if (!clockView12Skel.contains("a")) { 241 clockView12 = clockView12.replaceAll("a", "").trim(); 242 } 243 244 clockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel); 245 246 // Use fancy colon. 247 clockView24 = clockView24.replace(':', '\uee01'); 248 clockView12 = clockView12.replace(':', '\uee01'); 249 250 cacheKey = key; 251 } 252 } 253} 254