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