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; Helper(View v)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 setMessage(CharSequence msg, boolean important)93 public void setMessage(CharSequence msg, boolean important) { 94 if (!TextUtils.isEmpty(msg) && important) { 95 mMessageArea.mMessage = msg; 96 mMessageArea.securityMessageChanged(); 97 } 98 } 99 setMessage(int resId, boolean important)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 setMessage(int resId, boolean important, Object... formatArgs)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 showBouncer(int duration)115 public void showBouncer(int duration) { 116 mMessageArea.hideMessage(duration, false); 117 mMessageArea.mShowingBouncer = true; 118 } 119 120 @Override hideBouncer(int duration)121 public void hideBouncer(int duration) { 122 mMessageArea.showMessage(duration); 123 mMessageArea.mShowingBouncer = false; 124 } 125 126 @Override setTimeout(int timeoutMs)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 KeyguardMessageArea(Context context)141 public KeyguardMessageArea(Context context) { 142 this(context, null); 143 } 144 KeyguardMessageArea(Context context, AttributeSet attrs)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( 157 com.android.internal.R.string.kg_text_message_separator); 158 159 update(); 160 } 161 162 @Override onFinishInflate()163 protected void onFinishInflate() { 164 final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); 165 setSelected(screenOn); // This is required to ensure marquee works 166 } 167 securityMessageChanged()168 public void securityMessageChanged() { 169 setAlpha(1f); 170 mShowingMessage = true; 171 update(); 172 mHandler.removeCallbacks(mClearMessageRunnable); 173 if (mTimeout > 0) { 174 mHandler.postDelayed(mClearMessageRunnable, mTimeout); 175 } 176 mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN); 177 mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN, 178 (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY)); 179 } 180 181 /** 182 * Update the status lines based on these rules: 183 * AlarmStatus: Alarm state always gets it's own line. 184 * Status1 is shared between help, battery status and generic unlock instructions, 185 * prioritized in that order. 186 * @param showStatusLines status lines are shown if true 187 */ update()188 void update() { 189 MutableInt icon = new MutableInt(0); 190 CharSequence status = getCurrentMessage(); 191 setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0); 192 setText(status); 193 } 194 195 getCurrentMessage()196 CharSequence getCurrentMessage() { 197 return mShowingMessage ? mMessage : null; 198 } 199 hideMessage(int duration, boolean thenUpdate)200 private void hideMessage(int duration, boolean thenUpdate) { 201 if (duration > 0) { 202 Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f); 203 anim.setDuration(duration); 204 if (thenUpdate) { 205 anim.addListener(new AnimatorListenerAdapter() { 206 @Override 207 public void onAnimationEnd(Animator animation) { 208 update(); 209 } 210 }); 211 } 212 anim.start(); 213 } else { 214 setAlpha(0f); 215 if (thenUpdate) { 216 update(); 217 } 218 } 219 } 220 showMessage(int duration)221 private void showMessage(int duration) { 222 if (duration > 0) { 223 Animator anim = ObjectAnimator.ofFloat(this, "alpha", 1f); 224 anim.setDuration(duration); 225 anim.start(); 226 } else { 227 setAlpha(1f); 228 } 229 } 230 231 /** 232 * Runnable used to delay accessibility announcements. 233 */ 234 private static class AnnounceRunnable implements Runnable { 235 private final WeakReference<View> mHost; 236 private final CharSequence mTextToAnnounce; 237 AnnounceRunnable(View host, CharSequence textToAnnounce)238 public AnnounceRunnable(View host, CharSequence textToAnnounce) { 239 mHost = new WeakReference<View>(host); 240 mTextToAnnounce = textToAnnounce; 241 } 242 243 @Override run()244 public void run() { 245 final View host = mHost.get(); 246 if (host != null) { 247 host.announceForAccessibility(mTextToAnnounce); 248 } 249 } 250 } 251 } 252