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.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.Looper; 27 import android.os.SystemClock; 28 import android.text.TextUtils; 29 import android.util.AttributeSet; 30 import android.util.MutableInt; 31 import android.view.View; 32 import android.widget.TextView; 33 34 import java.lang.ref.WeakReference; 35 36 import com.android.internal.widget.LockPatternUtils; 37 38 /*** 39 * Manages a number of views inside of the given layout. See below for a list of widgets. 40 */ 41 class 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 SECURITY_MESSAGE_DURATION = 5000; 52 protected static final int FADE_DURATION = 750; 53 54 private static final String TAG = "KeyguardMessageArea"; 55 56 // is the bouncer up? 57 boolean mShowingBouncer = false; 58 59 KeyguardUpdateMonitor mUpdateMonitor; 60 61 // Timeout before we reset the message to show charging/owner info 62 long mTimeout = SECURITY_MESSAGE_DURATION; 63 64 private Handler mHandler; 65 66 CharSequence mMessage; 67 boolean mShowingMessage; 68 private CharSequence mSeparator; 69 private LockPatternUtils mLockPatternUtils; 70 71 Runnable mClearMessageRunnable = new Runnable() { 72 @Override 73 public void run() { 74 mMessage = null; 75 mShowingMessage = false; 76 if (mShowingBouncer) { 77 hideMessage(FADE_DURATION, true); 78 } else { 79 update(); 80 } 81 } 82 }; 83 84 public static class Helper implements SecurityMessageDisplay { 85 KeyguardMessageArea mMessageArea; 86 Helper(View v) { 87 mMessageArea = (KeyguardMessageArea) v.findViewById(R.id.keyguard_message_area); 88 if (mMessageArea == null) { 89 throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass()); 90 } 91 } 92 93 public void setMessage(CharSequence msg, boolean important) { 94 if (!TextUtils.isEmpty(msg) && important) { 95 mMessageArea.mMessage = msg; 96 mMessageArea.securityMessageChanged(); 97 } 98 } 99 100 public void setMessage(int resId, boolean important) { 101 if (resId != 0 && important) { 102 mMessageArea.mMessage = mMessageArea.getContext().getResources().getText(resId); 103 mMessageArea.securityMessageChanged(); 104 } 105 } 106 107 public void setMessage(int resId, boolean important, Object... formatArgs) { 108 if (resId != 0 && important) { 109 mMessageArea.mMessage = mMessageArea.getContext().getString(resId, formatArgs); 110 mMessageArea.securityMessageChanged(); 111 } 112 } 113 114 @Override 115 public void showBouncer(int duration) { 116 mMessageArea.hideMessage(duration, false); 117 mMessageArea.mShowingBouncer = true; 118 } 119 120 @Override 121 public void hideBouncer(int duration) { 122 mMessageArea.showMessage(duration); 123 mMessageArea.mShowingBouncer = false; 124 } 125 126 @Override 127 public void setTimeout(int timeoutMs) { 128 mMessageArea.mTimeout = timeoutMs; 129 } 130 } 131 132 private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { 133 public void onScreenTurnedOff(int why) { 134 setSelected(false); 135 }; 136 public void onScreenTurnedOn() { 137 setSelected(true); 138 }; 139 }; 140 141 public KeyguardMessageArea(Context context) { 142 this(context, null); 143 } 144 145 public KeyguardMessageArea(Context context, AttributeSet attrs) { 146 super(context, attrs); 147 setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug 148 149 mLockPatternUtils = new LockPatternUtils(context); 150 151 // Registering this callback immediately updates the battery state, among other things. 152 mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext()); 153 mUpdateMonitor.registerCallback(mInfoCallback); 154 mHandler = new Handler(Looper.myLooper()); 155 156 mSeparator = getResources().getString(R.string.kg_text_message_separator); 157 158 update(); 159 } 160 161 @Override 162 protected void onFinishInflate() { 163 final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); 164 setSelected(screenOn); // This is required to ensure marquee works 165 } 166 167 public void securityMessageChanged() { 168 setAlpha(1f); 169 mShowingMessage = true; 170 update(); 171 mHandler.removeCallbacks(mClearMessageRunnable); 172 if (mTimeout > 0) { 173 mHandler.postDelayed(mClearMessageRunnable, mTimeout); 174 } 175 mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN); 176 mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN, 177 (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY)); 178 } 179 180 /** 181 * Update the status lines based on these rules: 182 * AlarmStatus: Alarm state always gets it's own line. 183 * Status1 is shared between help, battery status and generic unlock instructions, 184 * prioritized in that order. 185 * @param showStatusLines status lines are shown if true 186 */ 187 void update() { 188 MutableInt icon = new MutableInt(0); 189 CharSequence status = getCurrentMessage(); 190 setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0); 191 setText(status); 192 } 193 194 195 CharSequence getCurrentMessage() { 196 return mShowingMessage ? mMessage : null; 197 } 198 199 private void hideMessage(int duration, boolean thenUpdate) { 200 if (duration > 0) { 201 Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f); 202 anim.setDuration(duration); 203 if (thenUpdate) { 204 anim.addListener(new AnimatorListenerAdapter() { 205 @Override 206 public void onAnimationEnd(Animator animation) { 207 update(); 208 } 209 }); 210 } 211 anim.start(); 212 } else { 213 setAlpha(0f); 214 if (thenUpdate) { 215 update(); 216 } 217 } 218 } 219 220 private void showMessage(int duration) { 221 if (duration > 0) { 222 Animator anim = ObjectAnimator.ofFloat(this, "alpha", 1f); 223 anim.setDuration(duration); 224 anim.start(); 225 } else { 226 setAlpha(1f); 227 } 228 } 229 230 /** 231 * Runnable used to delay accessibility announcements. 232 */ 233 private static class AnnounceRunnable implements Runnable { 234 private final WeakReference<View> mHost; 235 private final CharSequence mTextToAnnounce; 236 237 public AnnounceRunnable(View host, CharSequence textToAnnounce) { 238 mHost = new WeakReference<View>(host); 239 mTextToAnnounce = textToAnnounce; 240 } 241 242 @Override 243 public void run() { 244 final View host = mHost.get(); 245 if (host != null) { 246 host.announceForAccessibility(mTextToAnnounce); 247 } 248 } 249 } 250 } 251