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