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 static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; 20 21 import android.content.Context; 22 import android.content.res.ColorStateList; 23 import android.content.res.TypedArray; 24 import android.graphics.Color; 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.TypedValue; 31 import android.view.View; 32 import android.widget.TextView; 33 34 import com.android.systemui.R; 35 import com.android.systemui.statusbar.policy.ConfigurationController; 36 37 import java.lang.ref.WeakReference; 38 39 import javax.inject.Inject; 40 import javax.inject.Named; 41 42 /*** 43 * Manages a number of views inside of the given layout. See below for a list of widgets. 44 */ 45 public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay, 46 ConfigurationController.ConfigurationListener { 47 /** Handler token posted with accessibility announcement runnables. */ 48 private static final Object ANNOUNCE_TOKEN = new Object(); 49 50 /** 51 * Delay before speaking an accessibility announcement. Used to prevent 52 * lift-to-type from interrupting itself. 53 */ 54 private static final long ANNOUNCEMENT_DELAY = 250; 55 private static final int DEFAULT_COLOR = -1; 56 57 private final Handler mHandler; 58 private final ConfigurationController mConfigurationController; 59 60 private ColorStateList mDefaultColorState; 61 private CharSequence mMessage; 62 private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR); 63 private boolean mBouncerVisible; 64 65 private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { 66 public void onFinishedGoingToSleep(int why) { 67 setSelected(false); 68 } 69 70 public void onStartedWakingUp() { 71 setSelected(true); 72 } 73 74 @Override 75 public void onKeyguardBouncerChanged(boolean bouncer) { 76 mBouncerVisible = bouncer; 77 update(); 78 } 79 }; 80 KeyguardMessageArea(Context context)81 public KeyguardMessageArea(Context context) { 82 super(context, null); 83 throw new IllegalStateException("This constructor should never be invoked"); 84 } 85 86 @Inject KeyguardMessageArea(@amedVIEW_CONTEXT) Context context, AttributeSet attrs, ConfigurationController configurationController)87 public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, 88 ConfigurationController configurationController) { 89 this(context, attrs, KeyguardUpdateMonitor.getInstance(context), configurationController); 90 } 91 KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor, ConfigurationController configurationController)92 public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor, 93 ConfigurationController configurationController) { 94 super(context, attrs); 95 setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug 96 97 monitor.registerCallback(mInfoCallback); 98 mHandler = new Handler(Looper.myLooper()); 99 mConfigurationController = configurationController; 100 onThemeChanged(); 101 } 102 103 @Override onAttachedToWindow()104 protected void onAttachedToWindow() { 105 super.onAttachedToWindow(); 106 mConfigurationController.addCallback(this); 107 onThemeChanged(); 108 } 109 110 @Override onDetachedFromWindow()111 protected void onDetachedFromWindow() { 112 super.onDetachedFromWindow(); 113 mConfigurationController.removeCallback(this); 114 } 115 116 @Override setNextMessageColor(ColorStateList colorState)117 public void setNextMessageColor(ColorStateList colorState) { 118 mNextMessageColorState = colorState; 119 } 120 121 @Override onThemeChanged()122 public void onThemeChanged() { 123 TypedArray array = mContext.obtainStyledAttributes(new int[] { 124 R.attr.wallpaperTextColor 125 }); 126 ColorStateList newTextColors = ColorStateList.valueOf(array.getColor(0, Color.RED)); 127 array.recycle(); 128 mDefaultColorState = newTextColors; 129 update(); 130 } 131 132 @Override onDensityOrFontScaleChanged()133 public void onDensityOrFontScaleChanged() { 134 TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] { 135 android.R.attr.textSize 136 }); 137 setTextSize(TypedValue.COMPLEX_UNIT_PX, array.getDimensionPixelSize(0, 0)); 138 array.recycle(); 139 } 140 141 @Override setMessage(CharSequence msg)142 public void setMessage(CharSequence msg) { 143 if (!TextUtils.isEmpty(msg)) { 144 securityMessageChanged(msg); 145 } else { 146 clearMessage(); 147 } 148 } 149 150 @Override setMessage(int resId)151 public void setMessage(int resId) { 152 CharSequence message = null; 153 if (resId != 0) { 154 message = getContext().getResources().getText(resId); 155 } 156 setMessage(message); 157 } 158 159 @Override formatMessage(int resId, Object... formatArgs)160 public void formatMessage(int resId, Object... formatArgs) { 161 CharSequence message = null; 162 if (resId != 0) { 163 message = getContext().getString(resId, formatArgs); 164 } 165 setMessage(message); 166 } 167 findSecurityMessageDisplay(View v)168 public static KeyguardMessageArea findSecurityMessageDisplay(View v) { 169 KeyguardMessageArea messageArea = v.findViewById(R.id.keyguard_message_area); 170 if (messageArea == null) { 171 messageArea = v.getRootView().findViewById(R.id.keyguard_message_area); 172 } 173 if (messageArea == null) { 174 throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass()); 175 } 176 return messageArea; 177 } 178 179 @Override onFinishInflate()180 protected void onFinishInflate() { 181 boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); 182 setSelected(shouldMarquee); // This is required to ensure marquee works 183 } 184 securityMessageChanged(CharSequence message)185 private void securityMessageChanged(CharSequence message) { 186 mMessage = message; 187 update(); 188 mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN); 189 mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN, 190 (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY)); 191 } 192 clearMessage()193 private void clearMessage() { 194 mMessage = null; 195 update(); 196 } 197 update()198 private void update() { 199 CharSequence status = mMessage; 200 setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE); 201 setText(status); 202 ColorStateList colorState = mDefaultColorState; 203 if (mNextMessageColorState.getDefaultColor() != DEFAULT_COLOR) { 204 colorState = mNextMessageColorState; 205 mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR); 206 } 207 setTextColor(colorState); 208 } 209 210 211 /** 212 * Runnable used to delay accessibility announcements. 213 */ 214 private static class AnnounceRunnable implements Runnable { 215 private final WeakReference<View> mHost; 216 private final CharSequence mTextToAnnounce; 217 AnnounceRunnable(View host, CharSequence textToAnnounce)218 AnnounceRunnable(View host, CharSequence textToAnnounce) { 219 mHost = new WeakReference<View>(host); 220 mTextToAnnounce = textToAnnounce; 221 } 222 223 @Override run()224 public void run() { 225 final View host = mHost.get(); 226 if (host != null) { 227 host.announceForAccessibility(mTextToAnnounce); 228 } 229 } 230 } 231 } 232