KeyguardMessageArea.java revision cacbe1b1ef01aff8726726edaf43d0b449319afe
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.MutableInt; 36import android.util.Slog; 37import android.view.View; 38import android.widget.TextView; 39 40import java.lang.ref.WeakReference; 41 42import com.android.internal.widget.LockPatternUtils; 43 44/*** 45 * Manages a number of views inside of the given layout. See below for a list of widgets. 46 */ 47class KeyguardMessageArea extends TextView { 48 /** Handler token posted with accessibility announcement runnables. */ 49 private static final Object ANNOUNCE_TOKEN = new Object(); 50 51 /** 52 * Delay before speaking an accessibility announcement. Used to prevent 53 * lift-to-type from interrupting itself. 54 */ 55 private static final long ANNOUNCEMENT_DELAY = 250; 56 57 static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging; 58 static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery; 59 60 static final int SECURITY_MESSAGE_DURATION = 5000; 61 protected static final int FADE_DURATION = 750; 62 63 private static final String TAG = "KeyguardMessageArea"; 64 65 // are we showing battery information? 66 boolean mShowingBatteryInfo = false; 67 68 // is the bouncer up? 69 boolean mShowingBouncer = false; 70 71 // last known plugged in state 72 boolean mCharging = false; 73 74 // last known battery level 75 int mBatteryLevel = 100; 76 77 KeyguardUpdateMonitor mUpdateMonitor; 78 79 // Timeout before we reset the message to show charging/owner info 80 long mTimeout = SECURITY_MESSAGE_DURATION; 81 82 // Shadowed text values 83 protected boolean mBatteryCharged; 84 protected boolean mBatteryIsLow; 85 86 private Handler mHandler; 87 88 CharSequence mMessage; 89 boolean mShowingMessage; 90 private CharSequence mSeparator; 91 private LockPatternUtils mLockPatternUtils; 92 93 Runnable mClearMessageRunnable = new Runnable() { 94 @Override 95 public void run() { 96 mMessage = null; 97 mShowingMessage = false; 98 if (mShowingBouncer) { 99 hideMessage(FADE_DURATION, true); 100 } else { 101 update(); 102 } 103 } 104 }; 105 106 public static class Helper implements SecurityMessageDisplay { 107 KeyguardMessageArea mMessageArea; 108 Helper(View v) { 109 mMessageArea = (KeyguardMessageArea) v.findViewById(R.id.keyguard_message_area); 110 if (mMessageArea == null) { 111 throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass()); 112 } 113 } 114 115 public void setMessage(CharSequence msg, boolean important) { 116 if (!TextUtils.isEmpty(msg) && important) { 117 mMessageArea.mMessage = msg; 118 mMessageArea.securityMessageChanged(); 119 } 120 } 121 122 public void setMessage(int resId, boolean important) { 123 if (resId != 0 && important) { 124 mMessageArea.mMessage = mMessageArea.getContext().getResources().getText(resId); 125 mMessageArea.securityMessageChanged(); 126 } 127 } 128 129 public void setMessage(int resId, boolean important, Object... formatArgs) { 130 if (resId != 0 && important) { 131 mMessageArea.mMessage = mMessageArea.getContext().getString(resId, formatArgs); 132 mMessageArea.securityMessageChanged(); 133 } 134 } 135 136 @Override 137 public void showBouncer(int duration) { 138 mMessageArea.hideMessage(duration, false); 139 mMessageArea.mShowingBouncer = true; 140 } 141 142 @Override 143 public void hideBouncer(int duration) { 144 mMessageArea.showMessage(duration); 145 mMessageArea.mShowingBouncer = false; 146 } 147 148 @Override 149 public void setTimeout(int timeoutMs) { 150 mMessageArea.mTimeout = timeoutMs; 151 } 152 } 153 154 private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { 155 @Override 156 public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { 157 mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow(); 158 mCharging = status.status == BatteryManager.BATTERY_STATUS_CHARGING 159 || status.status == BatteryManager.BATTERY_STATUS_FULL; 160 mBatteryLevel = status.level; 161 mBatteryCharged = status.isCharged(); 162 mBatteryIsLow = status.isBatteryLow(); 163 update(); 164 } 165 public void onScreenTurnedOff(int why) { 166 setSelected(false); 167 }; 168 public void onScreenTurnedOn() { 169 setSelected(true); 170 }; 171 }; 172 173 public KeyguardMessageArea(Context context) { 174 this(context, null); 175 } 176 177 public KeyguardMessageArea(Context context, AttributeSet attrs) { 178 super(context, attrs); 179 setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug 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