/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.keyguard; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.statusbar.policy.ConfigurationController; import java.lang.ref.WeakReference; import javax.inject.Inject; import javax.inject.Named; /*** * Manages a number of views inside of the given layout. See below for a list of widgets. */ public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay, ConfigurationController.ConfigurationListener { /** Handler token posted with accessibility announcement runnables. */ private static final Object ANNOUNCE_TOKEN = new Object(); /** * Delay before speaking an accessibility announcement. Used to prevent * lift-to-type from interrupting itself. */ private static final long ANNOUNCEMENT_DELAY = 250; private static final int DEFAULT_COLOR = -1; private final Handler mHandler; private final ConfigurationController mConfigurationController; private ColorStateList mDefaultColorState; private CharSequence mMessage; private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR); private boolean mBouncerVisible; private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { public void onFinishedGoingToSleep(int why) { setSelected(false); } public void onStartedWakingUp() { setSelected(true); } @Override public void onKeyguardBouncerChanged(boolean bouncer) { mBouncerVisible = bouncer; update(); } }; public KeyguardMessageArea(Context context) { super(context, null); throw new IllegalStateException("This constructor should never be invoked"); } @Inject public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, ConfigurationController configurationController) { this(context, attrs, KeyguardUpdateMonitor.getInstance(context), configurationController); } public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor, ConfigurationController configurationController) { super(context, attrs); setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug monitor.registerCallback(mInfoCallback); mHandler = new Handler(Looper.myLooper()); mConfigurationController = configurationController; onThemeChanged(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mConfigurationController.addCallback(this); onThemeChanged(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mConfigurationController.removeCallback(this); } @Override public void setNextMessageColor(ColorStateList colorState) { mNextMessageColorState = colorState; } @Override public void onThemeChanged() { TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor }); ColorStateList newTextColors = ColorStateList.valueOf(array.getColor(0, Color.RED)); array.recycle(); mDefaultColorState = newTextColors; update(); } @Override public void onDensityOrFontScaleChanged() { TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] { android.R.attr.textSize }); setTextSize(TypedValue.COMPLEX_UNIT_PX, array.getDimensionPixelSize(0, 0)); array.recycle(); } @Override public void setMessage(CharSequence msg) { if (!TextUtils.isEmpty(msg)) { securityMessageChanged(msg); } else { clearMessage(); } } @Override public void setMessage(int resId) { CharSequence message = null; if (resId != 0) { message = getContext().getResources().getText(resId); } setMessage(message); } @Override public void formatMessage(int resId, Object... formatArgs) { CharSequence message = null; if (resId != 0) { message = getContext().getString(resId, formatArgs); } setMessage(message); } public static KeyguardMessageArea findSecurityMessageDisplay(View v) { KeyguardMessageArea messageArea = v.findViewById(R.id.keyguard_message_area); if (messageArea == null) { messageArea = v.getRootView().findViewById(R.id.keyguard_message_area); } if (messageArea == null) { throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass()); } return messageArea; } @Override protected void onFinishInflate() { boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); setSelected(shouldMarquee); // This is required to ensure marquee works } private void securityMessageChanged(CharSequence message) { mMessage = message; update(); mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN); mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN, (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY)); } private void clearMessage() { mMessage = null; update(); } private void update() { CharSequence status = mMessage; setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE); setText(status); ColorStateList colorState = mDefaultColorState; if (mNextMessageColorState.getDefaultColor() != DEFAULT_COLOR) { colorState = mNextMessageColorState; mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR); } setTextColor(colorState); } /** * Runnable used to delay accessibility announcements. */ private static class AnnounceRunnable implements Runnable { private final WeakReference mHost; private final CharSequence mTextToAnnounce; AnnounceRunnable(View host, CharSequence textToAnnounce) { mHost = new WeakReference(host); mTextToAnnounce = textToAnnounce; } @Override public void run() { final View host = mHost.get(); if (host != null) { host.announceForAccessibility(mTextToAnnounce); } } } }