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