KeyguardStatusView.java revision c239fefc1979049e7f29c5ee39e218d4439d693a
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.Context; 22import android.content.res.ColorStateList; 23import android.content.res.Configuration; 24import android.content.res.Resources; 25import android.graphics.Color; 26import android.graphics.PorterDuff; 27import android.os.Handler; 28import android.os.Looper; 29import android.os.UserHandle; 30import android.support.v4.graphics.ColorUtils; 31import android.text.TextUtils; 32import android.text.format.DateFormat; 33import android.util.AttributeSet; 34import android.util.Log; 35import android.util.Slog; 36import android.util.TypedValue; 37import android.view.View; 38import android.view.ViewGroup; 39import android.widget.GridLayout; 40import android.widget.TextClock; 41import android.widget.TextView; 42 43import com.android.internal.util.ArrayUtils; 44import com.android.internal.widget.LockPatternUtils; 45import com.android.systemui.ChargingView; 46import com.android.systemui.statusbar.policy.DateView; 47 48import java.util.Locale; 49 50public class KeyguardStatusView extends GridLayout { 51 private static final boolean DEBUG = KeyguardConstants.DEBUG; 52 private static final String TAG = "KeyguardStatusView"; 53 private static final int MARQUEE_DELAY_MS = 2000; 54 55 private final LockPatternUtils mLockPatternUtils; 56 private final AlarmManager mAlarmManager; 57 58 private TextView mAlarmStatusView; 59 private DateView mDateView; 60 private TextClock mClockView; 61 private TextView mOwnerInfo; 62 private ViewGroup mClockContainer; 63 private ChargingView mBatteryDoze; 64 private View mKeyguardStatusArea; 65 private Runnable mPendingMarqueeStart; 66 private Handler mHandler; 67 68 private View[] mVisibleInDoze; 69 private boolean mPulsing; 70 private float mDarkAmount = 0; 71 private int mTextColor; 72 private int mDateTextColor; 73 private int mAlarmTextColor; 74 75 private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { 76 77 @Override 78 public void onTimeChanged() { 79 refresh(); 80 } 81 82 @Override 83 public void onKeyguardVisibilityChanged(boolean showing) { 84 if (showing) { 85 if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing); 86 refresh(); 87 updateOwnerInfo(); 88 } 89 } 90 91 @Override 92 public void onStartedWakingUp() { 93 setEnableMarquee(true); 94 } 95 96 @Override 97 public void onFinishedGoingToSleep(int why) { 98 setEnableMarquee(false); 99 } 100 101 @Override 102 public void onUserSwitchComplete(int userId) { 103 refresh(); 104 updateOwnerInfo(); 105 } 106 }; 107 108 public KeyguardStatusView(Context context) { 109 this(context, null, 0); 110 } 111 112 public KeyguardStatusView(Context context, AttributeSet attrs) { 113 this(context, attrs, 0); 114 } 115 116 public KeyguardStatusView(Context context, AttributeSet attrs, int defStyle) { 117 super(context, attrs, defStyle); 118 mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 119 mLockPatternUtils = new LockPatternUtils(getContext()); 120 mHandler = new Handler(Looper.myLooper()); 121 } 122 123 private void setEnableMarquee(boolean enabled) { 124 if (DEBUG) Log.v(TAG, "Schedule setEnableMarquee: " + (enabled ? "Enable" : "Disable")); 125 if (enabled) { 126 if (mPendingMarqueeStart == null) { 127 mPendingMarqueeStart = () -> { 128 setEnableMarqueeImpl(true); 129 mPendingMarqueeStart = null; 130 }; 131 mHandler.postDelayed(mPendingMarqueeStart, MARQUEE_DELAY_MS); 132 } 133 } else { 134 if (mPendingMarqueeStart != null) { 135 mHandler.removeCallbacks(mPendingMarqueeStart); 136 mPendingMarqueeStart = null; 137 } 138 setEnableMarqueeImpl(false); 139 } 140 } 141 142 private void setEnableMarqueeImpl(boolean enabled) { 143 if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee"); 144 if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled); 145 if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled); 146 } 147 148 @Override 149 protected void onFinishInflate() { 150 super.onFinishInflate(); 151 mClockContainer = findViewById(R.id.keyguard_clock_container); 152 mAlarmStatusView = findViewById(R.id.alarm_status); 153 mDateView = findViewById(R.id.date_view); 154 mClockView = findViewById(R.id.clock_view); 155 mClockView.setShowCurrentUserTime(true); 156 mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext)); 157 mOwnerInfo = findViewById(R.id.owner_info); 158 mBatteryDoze = findViewById(R.id.battery_doze); 159 mKeyguardStatusArea = findViewById(R.id.keyguard_status_area); 160 mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardStatusArea}; 161 mTextColor = mClockView.getCurrentTextColor(); 162 mDateTextColor = mDateView.getCurrentTextColor(); 163 mAlarmTextColor = mAlarmStatusView.getCurrentTextColor(); 164 165 boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); 166 setEnableMarquee(shouldMarquee); 167 refresh(); 168 updateOwnerInfo(); 169 170 // Disable elegant text height because our fancy colon makes the ymin value huge for no 171 // reason. 172 mClockView.setElegantTextHeight(false); 173 } 174 175 @Override 176 protected void onConfigurationChanged(Configuration newConfig) { 177 super.onConfigurationChanged(newConfig); 178 mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 179 getResources().getDimensionPixelSize(R.dimen.widget_big_font_size)); 180 // Some layouts like burmese have a different margin for the clock 181 MarginLayoutParams layoutParams = (MarginLayoutParams) mClockView.getLayoutParams(); 182 layoutParams.bottomMargin = getResources().getDimensionPixelSize( 183 R.dimen.bottom_text_spacing_digital); 184 mClockView.setLayoutParams(layoutParams); 185 mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 186 getResources().getDimensionPixelSize(R.dimen.widget_label_font_size)); 187 if (mOwnerInfo != null) { 188 mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX, 189 getResources().getDimensionPixelSize(R.dimen.widget_label_font_size)); 190 } 191 } 192 193 public void refreshTime() { 194 mDateView.setDatePattern(Patterns.dateViewSkel); 195 196 mClockView.setFormat12Hour(Patterns.clockView12); 197 mClockView.setFormat24Hour(Patterns.clockView24); 198 } 199 200 private void refresh() { 201 AlarmManager.AlarmClockInfo nextAlarm = 202 mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT); 203 Patterns.update(mContext, nextAlarm != null); 204 205 refreshTime(); 206 refreshAlarmStatus(nextAlarm); 207 } 208 209 void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) { 210 if (nextAlarm != null) { 211 String alarm = formatNextAlarm(mContext, nextAlarm); 212 mAlarmStatusView.setText(alarm); 213 mAlarmStatusView.setContentDescription( 214 getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm)); 215 mAlarmStatusView.setVisibility(View.VISIBLE); 216 } else { 217 mAlarmStatusView.setVisibility(View.GONE); 218 } 219 } 220 221 public int getClockBottom() { 222 return mKeyguardStatusArea.getBottom(); 223 } 224 225 public float getClockTextSize() { 226 return mClockView.getTextSize(); 227 } 228 229 public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) { 230 if (info == null) { 231 return ""; 232 } 233 String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser()) 234 ? "EHm" 235 : "Ehma"; 236 String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 237 return DateFormat.format(pattern, info.getTriggerTime()).toString(); 238 } 239 240 private void updateOwnerInfo() { 241 if (mOwnerInfo == null) return; 242 String ownerInfo = getOwnerInfo(); 243 if (!TextUtils.isEmpty(ownerInfo)) { 244 mOwnerInfo.setVisibility(View.VISIBLE); 245 mOwnerInfo.setText(ownerInfo); 246 } else { 247 mOwnerInfo.setVisibility(View.GONE); 248 } 249 } 250 251 @Override 252 protected void onAttachedToWindow() { 253 super.onAttachedToWindow(); 254 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mInfoCallback); 255 } 256 257 @Override 258 protected void onDetachedFromWindow() { 259 super.onDetachedFromWindow(); 260 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mInfoCallback); 261 } 262 263 private String getOwnerInfo() { 264 String info = null; 265 if (mLockPatternUtils.isDeviceOwnerInfoEnabled()) { 266 // Use the device owner information set by device policy client via 267 // device policy manager. 268 info = mLockPatternUtils.getDeviceOwnerInfo(); 269 } else { 270 // Use the current user owner information if enabled. 271 final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled( 272 KeyguardUpdateMonitor.getCurrentUser()); 273 if (ownerInfoEnabled) { 274 info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); 275 } 276 } 277 return info; 278 } 279 280 @Override 281 public boolean hasOverlappingRendering() { 282 return false; 283 } 284 285 // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. 286 // This is an optimization to ensure we only recompute the patterns when the inputs change. 287 private static final class Patterns { 288 static String dateViewSkel; 289 static String clockView12; 290 static String clockView24; 291 static String cacheKey; 292 293 static void update(Context context, boolean hasAlarm) { 294 final Locale locale = Locale.getDefault(); 295 final Resources res = context.getResources(); 296 dateViewSkel = res.getString(hasAlarm 297 ? R.string.abbrev_wday_month_day_no_year_alarm 298 : R.string.abbrev_wday_month_day_no_year); 299 final String clockView12Skel = res.getString(R.string.clock_12hr_format); 300 final String clockView24Skel = res.getString(R.string.clock_24hr_format); 301 final String key = locale.toString() + dateViewSkel + clockView12Skel + clockView24Skel; 302 if (key.equals(cacheKey)) return; 303 304 clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel); 305 // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton 306 // format. The following code removes the AM/PM indicator if we didn't want it. 307 if (!clockView12Skel.contains("a")) { 308 clockView12 = clockView12.replaceAll("a", "").trim(); 309 } 310 311 clockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel); 312 313 // Use fancy colon. 314 clockView24 = clockView24.replace(':', '\uee01'); 315 clockView12 = clockView12.replace(':', '\uee01'); 316 317 cacheKey = key; 318 } 319 } 320 321 public void setDark(float darkAmount) { 322 if (mDarkAmount == darkAmount) { 323 return; 324 } 325 mDarkAmount = darkAmount; 326 327 boolean dark = darkAmount == 1; 328 final int N = mClockContainer.getChildCount(); 329 for (int i = 0; i < N; i++) { 330 View child = mClockContainer.getChildAt(i); 331 if (ArrayUtils.contains(mVisibleInDoze, child)) { 332 continue; 333 } 334 child.setAlpha(dark ? 0 : 1); 335 } 336 updateDozeVisibleViews(); 337 mBatteryDoze.setDark(dark); 338 mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount)); 339 mDateView.setTextColor(ColorUtils.blendARGB(mDateTextColor, Color.WHITE, darkAmount)); 340 int blendedAlarmColor = ColorUtils.blendARGB(mAlarmTextColor, Color.WHITE, darkAmount); 341 mAlarmStatusView.setTextColor(blendedAlarmColor); 342 mAlarmStatusView.setCompoundDrawableTintList(ColorStateList.valueOf(blendedAlarmColor)); 343 } 344 345 public void setPulsing(boolean pulsing) { 346 mPulsing = pulsing; 347 updateDozeVisibleViews(); 348 } 349 350 private void updateDozeVisibleViews() { 351 for (View child : mVisibleInDoze) { 352 child.setAlpha(mDarkAmount == 1 && mPulsing ? 0.8f : 1); 353 } 354 } 355} 356