KeyguardMessageArea.java revision 5ecd81154fa039961f65bb4e36d18ac555b0d1d6
1/* 2 * Copyright (C) 2011 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.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.os.BatteryManager; 25import android.os.Handler; 26import android.os.Looper; 27import android.os.SystemClock; 28import android.os.UserHandle; 29import android.provider.Settings; 30import android.text.TextUtils; 31import android.util.AttributeSet; 32import android.view.View; 33import android.widget.TextView; 34 35import libcore.util.MutableInt; 36 37import java.lang.ref.WeakReference; 38/*** 39 * Manages a number of views inside of the given layout. See below for a list of widgets. 40 */ 41class KeyguardMessageArea extends TextView { 42 /** Handler token posted with accessibility announcement runnables. */ 43 private static final Object ANNOUNCE_TOKEN = new Object(); 44 45 /** 46 * Delay before speaking an accessibility announcement. Used to prevent 47 * lift-to-type from interrupting itself. 48 */ 49 private static final long ANNOUNCEMENT_DELAY = 250; 50 51 static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging; 52 static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery; 53 54 static final int SECURITY_MESSAGE_DURATION = 5000; 55 protected static final int FADE_DURATION = 750; 56 57 // are we showing battery information? 58 boolean mShowingBatteryInfo = false; 59 60 // is the bouncer up? 61 boolean mShowingBouncer = false; 62 63 // last known plugged in state 64 boolean mCharging = false; 65 66 // last known battery level 67 int mBatteryLevel = 100; 68 69 KeyguardUpdateMonitor mUpdateMonitor; 70 71 // Timeout before we reset the message to show charging/owner info 72 long mTimeout = SECURITY_MESSAGE_DURATION; 73 74 // Shadowed text values 75 protected boolean mBatteryCharged; 76 protected boolean mBatteryIsLow; 77 78 private Handler mHandler; 79 80 CharSequence mMessage; 81 boolean mShowingMessage; 82 Runnable mClearMessageRunnable = new Runnable() { 83 @Override 84 public void run() { 85 mMessage = null; 86 mShowingMessage = false; 87 if (mShowingBouncer) { 88 hideMessage(FADE_DURATION, true); 89 } else { 90 update(); 91 } 92 } 93 }; 94 95 public static class Helper implements SecurityMessageDisplay { 96 KeyguardMessageArea mMessageArea; 97 Helper(View v) { 98 mMessageArea = (KeyguardMessageArea) v.findViewById(R.id.keyguard_message_area); 99 if (mMessageArea == null) { 100 throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass()); 101 } 102 } 103 104 public void setMessage(CharSequence msg, boolean important) { 105 if (!TextUtils.isEmpty(msg) && important) { 106 mMessageArea.mMessage = msg; 107 mMessageArea.securityMessageChanged(); 108 } 109 } 110 111 public void setMessage(int resId, boolean important) { 112 if (resId != 0 && important) { 113 mMessageArea.mMessage = mMessageArea.getContext().getResources().getText(resId); 114 mMessageArea.securityMessageChanged(); 115 } 116 } 117 118 public void setMessage(int resId, boolean important, Object... formatArgs) { 119 if (resId != 0 && important) { 120 mMessageArea.mMessage = mMessageArea.getContext().getString(resId, formatArgs); 121 mMessageArea.securityMessageChanged(); 122 } 123 } 124 125 @Override 126 public void showBouncer(int duration) { 127 mMessageArea.hideMessage(duration, false); 128 mMessageArea.mShowingBouncer = true; 129 } 130 131 @Override 132 public void hideBouncer(int duration) { 133 mMessageArea.showMessage(duration); 134 mMessageArea.mShowingBouncer = false; 135 } 136 137 @Override 138 public void setTimeout(int timeoutMs) { 139 mMessageArea.mTimeout = timeoutMs; 140 } 141 } 142 143 private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { 144 @Override 145 public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { 146 mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow(); 147 mCharging = status.status == BatteryManager.BATTERY_STATUS_CHARGING 148 || status.status == BatteryManager.BATTERY_STATUS_FULL; 149 mBatteryLevel = status.level; 150 mBatteryCharged = status.isCharged(); 151 mBatteryIsLow = status.isBatteryLow(); 152 update(); 153 } 154 }; 155 156 private CharSequence mSeparator; 157 158 public KeyguardMessageArea(Context context) { 159 this(context, null); 160 } 161 162 public KeyguardMessageArea(Context context, AttributeSet attrs) { 163 super(context, attrs); 164 165 // This is required to ensure marquee works 166 setSelected(true); 167 168 // Registering this callback immediately updates the battery state, among other things. 169 mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext()); 170 mUpdateMonitor.registerCallback(mInfoCallback); 171 mHandler = new Handler(Looper.myLooper()); 172 173 mSeparator = getResources().getString(R.string.kg_text_message_separator); 174 175 update(); 176 } 177 178 public void securityMessageChanged() { 179 setAlpha(1f); 180 mShowingMessage = true; 181 update(); 182 mHandler.removeCallbacks(mClearMessageRunnable); 183 if (mTimeout > 0) { 184 mHandler.postDelayed(mClearMessageRunnable, mTimeout); 185 } 186 mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN); 187 mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN, 188 (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY)); 189 } 190 191 /** 192 * Update the status lines based on these rules: 193 * AlarmStatus: Alarm state always gets it's own line. 194 * Status1 is shared between help, battery status and generic unlock instructions, 195 * prioritized in that order. 196 * @param showStatusLines status lines are shown if true 197 */ 198 void update() { 199 MutableInt icon = new MutableInt(0); 200 CharSequence status = concat(getChargeInfo(icon), getOwnerInfo(), getCurrentMessage()); 201 setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0); 202 setText(status); 203 } 204 205 private CharSequence concat(CharSequence... args) { 206 StringBuilder b = new StringBuilder(); 207 if (!TextUtils.isEmpty(args[0])) { 208 b.append(args[0]); 209 } 210 for (int i = 1; i < args.length; i++) { 211 CharSequence text = args[i]; 212 if (!TextUtils.isEmpty(text)) { 213 if (b.length() > 0) { 214 b.append(mSeparator); 215 } 216 b.append(text); 217 } 218 } 219 return b.toString(); 220 } 221 222 CharSequence getCurrentMessage() { 223 return mShowingMessage ? mMessage : null; 224 } 225 226 String getOwnerInfo() { 227 ContentResolver res = getContext().getContentResolver(); 228 final boolean ownerInfoEnabled = Settings.Secure.getIntForUser(res, 229 Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1, UserHandle.USER_CURRENT) != 0; 230 return ownerInfoEnabled && !mShowingMessage ? 231 Settings.Secure.getStringForUser(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO, 232 UserHandle.USER_CURRENT) : null; 233 } 234 235 private CharSequence getChargeInfo(MutableInt icon) { 236 CharSequence string = null; 237 if (mShowingBatteryInfo && !mShowingMessage) { 238 // Battery status 239 if (mCharging) { 240 // Charging, charged or waiting to charge. 241 string = getContext().getString(mBatteryCharged 242 ? R.string.keyguard_charged 243 : R.string.keyguard_plugged_in, mBatteryLevel); 244 icon.value = CHARGING_ICON; 245 } else if (mBatteryIsLow) { 246 // Battery is low 247 string = getContext().getString(R.string.keyguard_low_battery); 248 icon.value = BATTERY_LOW_ICON; 249 } 250 } 251 return string; 252 } 253 254 private void hideMessage(int duration, boolean thenUpdate) { 255 if (duration > 0) { 256 Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f); 257 anim.setDuration(duration); 258 if (thenUpdate) { 259 anim.addListener(new AnimatorListenerAdapter() { 260 @Override 261 public void onAnimationEnd(Animator animation) { 262 update(); 263 } 264 }); 265 } 266 anim.start(); 267 } else { 268 setAlpha(0f); 269 if (thenUpdate) { 270 update(); 271 } 272 } 273 } 274 275 private void showMessage(int duration) { 276 if (duration > 0) { 277 Animator anim = ObjectAnimator.ofFloat(this, "alpha", 1f); 278 anim.setDuration(duration); 279 anim.start(); 280 } else { 281 setAlpha(1f); 282 } 283 } 284 285 /** 286 * Runnable used to delay accessibility announcements. 287 */ 288 private static class AnnounceRunnable implements Runnable { 289 private final WeakReference<View> mHost; 290 private final CharSequence mTextToAnnounce; 291 292 public AnnounceRunnable(View host, CharSequence textToAnnounce) { 293 mHost = new WeakReference<View>(host); 294 mTextToAnnounce = textToAnnounce; 295 } 296 297 @Override 298 public void run() { 299 final View host = mHost.get(); 300 if (host != null) { 301 host.announceForAccessibility(mTextToAnnounce); 302 } 303 } 304 } 305} 306