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 // Some layouts like burmese have a different margin for the clock 131 MarginLayoutParams layoutParams = (MarginLayoutParams) mClockView.getLayoutParams(); 132 layoutParams.bottomMargin = getResources().getDimensionPixelSize( 133 R.dimen.bottom_text_spacing_digital); 134 mClockView.setLayoutParams(layoutParams); 135 mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 136 getResources().getDimensionPixelSize(R.dimen.widget_label_font_size)); 137 mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX, 138 getResources().getDimensionPixelSize(R.dimen.widget_label_font_size)); 139 } 140 refreshTime()141 public void refreshTime() { 142 mDateView.setFormat24Hour(Patterns.dateView); 143 mDateView.setFormat12Hour(Patterns.dateView); 144 145 mClockView.setFormat12Hour(Patterns.clockView12); 146 mClockView.setFormat24Hour(Patterns.clockView24); 147 } 148 refresh()149 private void refresh() { 150 AlarmManager.AlarmClockInfo nextAlarm = 151 mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT); 152 Patterns.update(mContext, nextAlarm != null); 153 154 refreshTime(); 155 refreshAlarmStatus(nextAlarm); 156 } 157 refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm)158 void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) { 159 if (nextAlarm != null) { 160 String alarm = formatNextAlarm(mContext, nextAlarm); 161 mAlarmStatusView.setText(alarm); 162 mAlarmStatusView.setContentDescription( 163 getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm)); 164 mAlarmStatusView.setVisibility(View.VISIBLE); 165 } else { 166 mAlarmStatusView.setVisibility(View.GONE); 167 } 168 } 169 formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info)170 public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) { 171 if (info == null) { 172 return ""; 173 } 174 String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser()) 175 ? "EHm" 176 : "Ehma"; 177 String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 178 return DateFormat.format(pattern, info.getTriggerTime()).toString(); 179 } 180 updateOwnerInfo()181 private void updateOwnerInfo() { 182 if (mOwnerInfo == null) return; 183 String ownerInfo = getOwnerInfo(); 184 if (!TextUtils.isEmpty(ownerInfo)) { 185 mOwnerInfo.setVisibility(View.VISIBLE); 186 mOwnerInfo.setText(ownerInfo); 187 } else { 188 mOwnerInfo.setVisibility(View.GONE); 189 } 190 } 191 192 @Override onAttachedToWindow()193 protected void onAttachedToWindow() { 194 super.onAttachedToWindow(); 195 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mInfoCallback); 196 } 197 198 @Override onDetachedFromWindow()199 protected void onDetachedFromWindow() { 200 super.onDetachedFromWindow(); 201 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mInfoCallback); 202 } 203 getOwnerInfo()204 private String getOwnerInfo() { 205 String info = null; 206 if (mLockPatternUtils.isDeviceOwnerInfoEnabled()) { 207 // Use the device owner information set by device policy client via 208 // device policy manager. 209 info = mLockPatternUtils.getDeviceOwnerInfo(); 210 } else { 211 // Use the current user owner information if enabled. 212 final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled( 213 KeyguardUpdateMonitor.getCurrentUser()); 214 if (ownerInfoEnabled) { 215 info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); 216 } 217 } 218 return info; 219 } 220 221 @Override hasOverlappingRendering()222 public boolean hasOverlappingRendering() { 223 return false; 224 } 225 226 // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. 227 // This is an optimization to ensure we only recompute the patterns when the inputs change. 228 private static final class Patterns { 229 static String dateView; 230 static String clockView12; 231 static String clockView24; 232 static String cacheKey; 233 update(Context context, boolean hasAlarm)234 static void update(Context context, boolean hasAlarm) { 235 final Locale locale = Locale.getDefault(); 236 final Resources res = context.getResources(); 237 final String dateViewSkel = res.getString(hasAlarm 238 ? R.string.abbrev_wday_month_day_no_year_alarm 239 : R.string.abbrev_wday_month_day_no_year); 240 final String clockView12Skel = res.getString(R.string.clock_12hr_format); 241 final String clockView24Skel = res.getString(R.string.clock_24hr_format); 242 final String key = locale.toString() + dateViewSkel + clockView12Skel + clockView24Skel; 243 if (key.equals(cacheKey)) return; 244 245 dateView = DateFormat.getBestDateTimePattern(locale, dateViewSkel); 246 247 clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel); 248 // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton 249 // format. The following code removes the AM/PM indicator if we didn't want it. 250 if (!clockView12Skel.contains("a")) { 251 clockView12 = clockView12.replaceAll("a", "").trim(); 252 } 253 254 clockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel); 255 256 // Use fancy colon. 257 clockView24 = clockView24.replace(':', '\uee01'); 258 clockView12 = clockView12.replace(':', '\uee01'); 259 260 cacheKey = key; 261 } 262 } 263 } 264