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