/* * 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 android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.ContentResolver; import android.content.Context; import android.os.BatteryManager; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.text.TextUtils; import android.util.AttributeSet; import android.util.MutableInt; import android.view.View; import android.widget.TextView; import java.lang.ref.WeakReference; import com.android.internal.widget.LockPatternUtils; /*** * Manages a number of views inside of the given layout. See below for a list of widgets. */ class KeyguardMessageArea extends TextView { /** 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; static final int SECURITY_MESSAGE_DURATION = 5000; protected static final int FADE_DURATION = 750; private static final String TAG = "KeyguardMessageArea"; // is the bouncer up? boolean mShowingBouncer = false; KeyguardUpdateMonitor mUpdateMonitor; // Timeout before we reset the message to show charging/owner info long mTimeout = SECURITY_MESSAGE_DURATION; private Handler mHandler; CharSequence mMessage; boolean mShowingMessage; private CharSequence mSeparator; private LockPatternUtils mLockPatternUtils; Runnable mClearMessageRunnable = new Runnable() { @Override public void run() { mMessage = null; mShowingMessage = false; if (mShowingBouncer) { hideMessage(FADE_DURATION, true); } else { update(); } } }; public static class Helper implements SecurityMessageDisplay { KeyguardMessageArea mMessageArea; Helper(View v) { mMessageArea = (KeyguardMessageArea) v.findViewById(R.id.keyguard_message_area); if (mMessageArea == null) { throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass()); } } public void setMessage(CharSequence msg, boolean important) { if (!TextUtils.isEmpty(msg) && important) { mMessageArea.mMessage = msg; mMessageArea.securityMessageChanged(); } } public void setMessage(int resId, boolean important) { if (resId != 0 && important) { mMessageArea.mMessage = mMessageArea.getContext().getResources().getText(resId); mMessageArea.securityMessageChanged(); } } public void setMessage(int resId, boolean important, Object... formatArgs) { if (resId != 0 && important) { mMessageArea.mMessage = mMessageArea.getContext().getString(resId, formatArgs); mMessageArea.securityMessageChanged(); } } @Override public void showBouncer(int duration) { mMessageArea.hideMessage(duration, false); mMessageArea.mShowingBouncer = true; } @Override public void hideBouncer(int duration) { mMessageArea.showMessage(duration); mMessageArea.mShowingBouncer = false; } @Override public void setTimeout(int timeoutMs) { mMessageArea.mTimeout = timeoutMs; } } private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { public void onScreenTurnedOff(int why) { setSelected(false); }; public void onScreenTurnedOn() { setSelected(true); }; }; public KeyguardMessageArea(Context context) { this(context, null); } public KeyguardMessageArea(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug mLockPatternUtils = new LockPatternUtils(context); // Registering this callback immediately updates the battery state, among other things. mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext()); mUpdateMonitor.registerCallback(mInfoCallback); mHandler = new Handler(Looper.myLooper()); mSeparator = getResources().getString( com.android.internal.R.string.kg_text_message_separator); update(); } @Override protected void onFinishInflate() { final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); setSelected(screenOn); // This is required to ensure marquee works } public void securityMessageChanged() { setAlpha(1f); mShowingMessage = true; update(); mHandler.removeCallbacks(mClearMessageRunnable); if (mTimeout > 0) { mHandler.postDelayed(mClearMessageRunnable, mTimeout); } mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN); mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN, (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY)); } /** * Update the status lines based on these rules: * AlarmStatus: Alarm state always gets it's own line. * Status1 is shared between help, battery status and generic unlock instructions, * prioritized in that order. * @param showStatusLines status lines are shown if true */ void update() { MutableInt icon = new MutableInt(0); CharSequence status = getCurrentMessage(); setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0); setText(status); } CharSequence getCurrentMessage() { return mShowingMessage ? mMessage : null; } private void hideMessage(int duration, boolean thenUpdate) { if (duration > 0) { Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f); anim.setDuration(duration); if (thenUpdate) { anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { update(); } }); } anim.start(); } else { setAlpha(0f); if (thenUpdate) { update(); } } } private void showMessage(int duration) { if (duration > 0) { Animator anim = ObjectAnimator.ofFloat(this, "alpha", 1f); anim.setDuration(duration); anim.start(); } else { setAlpha(1f); } } /** * Runnable used to delay accessibility announcements. */ private static class AnnounceRunnable implements Runnable { private final WeakReference mHost; private final CharSequence mTextToAnnounce; public 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); } } } }