Home | History | Annotate | Download | only in keyguard
      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.internal.policy.impl.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.IBinder;
     27 import android.os.Looper;
     28 import android.os.RemoteException;
     29 import android.os.ServiceManager;
     30 import android.os.SystemClock;
     31 import android.os.UserHandle;
     32 import android.provider.Settings;
     33 import android.text.TextUtils;
     34 import android.util.AttributeSet;
     35 import android.util.Slog;
     36 import android.view.View;
     37 import android.widget.TextView;
     38 
     39 import libcore.util.MutableInt;
     40 
     41 import java.lang.ref.WeakReference;
     42 
     43 import com.android.internal.R;
     44 import com.android.internal.widget.ILockSettings;
     45 import com.android.internal.widget.LockPatternUtils;
     46 
     47 /***
     48  * Manages a number of views inside of the given layout. See below for a list of widgets.
     49  */
     50 class KeyguardMessageArea extends TextView {
     51     /** Handler token posted with accessibility announcement runnables. */
     52     private static final Object ANNOUNCE_TOKEN = new Object();
     53 
     54     /**
     55      * Delay before speaking an accessibility announcement. Used to prevent
     56      * lift-to-type from interrupting itself.
     57      */
     58     private static final long ANNOUNCEMENT_DELAY = 250;
     59 
     60     static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging;
     61     static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery;
     62 
     63     static final int SECURITY_MESSAGE_DURATION = 5000;
     64     protected static final int FADE_DURATION = 750;
     65 
     66     private static final String TAG = "KeyguardMessageArea";
     67 
     68     // are we showing battery information?
     69     boolean mShowingBatteryInfo = false;
     70 
     71     // is the bouncer up?
     72     boolean mShowingBouncer = false;
     73 
     74     // last known plugged in state
     75     boolean mCharging = false;
     76 
     77     // last known battery level
     78     int mBatteryLevel = 100;
     79 
     80     KeyguardUpdateMonitor mUpdateMonitor;
     81 
     82     // Timeout before we reset the message to show charging/owner info
     83     long mTimeout = SECURITY_MESSAGE_DURATION;
     84 
     85     // Shadowed text values
     86     protected boolean mBatteryCharged;
     87     protected boolean mBatteryIsLow;
     88 
     89     private Handler mHandler;
     90 
     91     CharSequence mMessage;
     92     boolean mShowingMessage;
     93     private CharSequence mSeparator;
     94     private LockPatternUtils mLockPatternUtils;
     95 
     96     Runnable mClearMessageRunnable = new Runnable() {
     97         @Override
     98         public void run() {
     99             mMessage = null;
    100             mShowingMessage = false;
    101             if (mShowingBouncer) {
    102                 hideMessage(FADE_DURATION, true);
    103             } else {
    104                 update();
    105             }
    106         }
    107     };
    108 
    109     public static class Helper implements SecurityMessageDisplay {
    110         KeyguardMessageArea mMessageArea;
    111         Helper(View v) {
    112             mMessageArea = (KeyguardMessageArea) v.findViewById(R.id.keyguard_message_area);
    113             if (mMessageArea == null) {
    114                 throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass());
    115             }
    116         }
    117 
    118         public void setMessage(CharSequence msg, boolean important) {
    119             if (!TextUtils.isEmpty(msg) && important) {
    120                 mMessageArea.mMessage = msg;
    121                 mMessageArea.securityMessageChanged();
    122             }
    123         }
    124 
    125         public void setMessage(int resId, boolean important) {
    126             if (resId != 0 && important) {
    127                 mMessageArea.mMessage = mMessageArea.getContext().getResources().getText(resId);
    128                 mMessageArea.securityMessageChanged();
    129             }
    130         }
    131 
    132         public void setMessage(int resId, boolean important, Object... formatArgs) {
    133             if (resId != 0 && important) {
    134                 mMessageArea.mMessage = mMessageArea.getContext().getString(resId, formatArgs);
    135                 mMessageArea.securityMessageChanged();
    136             }
    137         }
    138 
    139         @Override
    140         public void showBouncer(int duration) {
    141             mMessageArea.hideMessage(duration, false);
    142             mMessageArea.mShowingBouncer = true;
    143         }
    144 
    145         @Override
    146         public void hideBouncer(int duration) {
    147             mMessageArea.showMessage(duration);
    148             mMessageArea.mShowingBouncer = false;
    149         }
    150 
    151         @Override
    152         public void setTimeout(int timeoutMs) {
    153             mMessageArea.mTimeout = timeoutMs;
    154         }
    155     }
    156 
    157     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
    158         @Override
    159         public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
    160             mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow();
    161             mCharging = status.status == BatteryManager.BATTERY_STATUS_CHARGING
    162                      || status.status == BatteryManager.BATTERY_STATUS_FULL;
    163             mBatteryLevel = status.level;
    164             mBatteryCharged = status.isCharged();
    165             mBatteryIsLow = status.isBatteryLow();
    166             update();
    167         }
    168     };
    169 
    170     public KeyguardMessageArea(Context context) {
    171         this(context, null);
    172     }
    173 
    174     public KeyguardMessageArea(Context context, AttributeSet attrs) {
    175         super(context, attrs);
    176 
    177         mLockPatternUtils = new LockPatternUtils(context);
    178 
    179         // This is required to ensure marquee works
    180         setSelected(true);
    181 
    182         // Registering this callback immediately updates the battery state, among other things.
    183         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
    184         mUpdateMonitor.registerCallback(mInfoCallback);
    185         mHandler = new Handler(Looper.myLooper());
    186 
    187         mSeparator = getResources().getString(R.string.kg_text_message_separator);
    188 
    189         update();
    190     }
    191 
    192     public void securityMessageChanged() {
    193         setAlpha(1f);
    194         mShowingMessage = true;
    195         update();
    196         mHandler.removeCallbacks(mClearMessageRunnable);
    197         if (mTimeout > 0) {
    198             mHandler.postDelayed(mClearMessageRunnable, mTimeout);
    199         }
    200         mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
    201         mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
    202                 (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
    203     }
    204 
    205     /**
    206      * Update the status lines based on these rules:
    207      * AlarmStatus: Alarm state always gets it's own line.
    208      * Status1 is shared between help, battery status and generic unlock instructions,
    209      * prioritized in that order.
    210      * @param showStatusLines status lines are shown if true
    211      */
    212     void update() {
    213         MutableInt icon = new MutableInt(0);
    214         CharSequence status = concat(getChargeInfo(icon), getOwnerInfo(), getCurrentMessage());
    215         setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0);
    216         setText(status);
    217     }
    218 
    219     private CharSequence concat(CharSequence... args) {
    220         StringBuilder b = new StringBuilder();
    221         if (!TextUtils.isEmpty(args[0])) {
    222             b.append(args[0]);
    223         }
    224         for (int i = 1; i < args.length; i++) {
    225             CharSequence text = args[i];
    226             if (!TextUtils.isEmpty(text)) {
    227                 if (b.length() > 0) {
    228                     b.append(mSeparator);
    229                 }
    230                 b.append(text);
    231             }
    232         }
    233         return b.toString();
    234     }
    235 
    236     CharSequence getCurrentMessage() {
    237         return mShowingMessage ? mMessage : null;
    238     }
    239 
    240     String getOwnerInfo() {
    241         ContentResolver res = getContext().getContentResolver();
    242         String info = null;
    243         final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled();
    244         if (ownerInfoEnabled && !mShowingMessage) {
    245             info = mLockPatternUtils.getOwnerInfo(mLockPatternUtils.getCurrentUser());
    246         }
    247         return info;
    248     }
    249 
    250     private CharSequence getChargeInfo(MutableInt icon) {
    251         CharSequence string = null;
    252         if (mShowingBatteryInfo && !mShowingMessage) {
    253             // Battery status
    254             if (mCharging) {
    255                 // Charging, charged or waiting to charge.
    256                 string = getContext().getString(mBatteryCharged
    257                         ? com.android.internal.R.string.lockscreen_charged
    258                         : com.android.internal.R.string.lockscreen_plugged_in, mBatteryLevel);
    259                 icon.value = CHARGING_ICON;
    260             } else if (mBatteryIsLow) {
    261                 // Battery is low
    262                 string = getContext().getString(
    263                         com.android.internal.R.string.lockscreen_low_battery);
    264                 icon.value = BATTERY_LOW_ICON;
    265             }
    266         }
    267         return string;
    268     }
    269 
    270     private void hideMessage(int duration, boolean thenUpdate) {
    271         if (duration > 0) {
    272             Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f);
    273             anim.setDuration(duration);
    274             if (thenUpdate) {
    275                 anim.addListener(new AnimatorListenerAdapter() {
    276                         @Override
    277                             public void onAnimationEnd(Animator animation) {
    278                             update();
    279                         }
    280                 });
    281             }
    282             anim.start();
    283         } else {
    284             setAlpha(0f);
    285             if (thenUpdate) {
    286                 update();
    287             }
    288         }
    289     }
    290 
    291     private void showMessage(int duration) {
    292         if (duration > 0) {
    293             Animator anim = ObjectAnimator.ofFloat(this, "alpha", 1f);
    294             anim.setDuration(duration);
    295             anim.start();
    296         } else {
    297             setAlpha(1f);
    298         }
    299     }
    300 
    301     /**
    302      * Runnable used to delay accessibility announcements.
    303      */
    304     private static class AnnounceRunnable implements Runnable {
    305         private final WeakReference<View> mHost;
    306         private final CharSequence mTextToAnnounce;
    307 
    308         public AnnounceRunnable(View host, CharSequence textToAnnounce) {
    309             mHost = new WeakReference<View>(host);
    310             mTextToAnnounce = textToAnnounce;
    311         }
    312 
    313         @Override
    314         public void run() {
    315             final View host = mHost.get();
    316             if (host != null) {
    317                 host.announceForAccessibility(mTextToAnnounce);
    318             }
    319         }
    320     }
    321 }
    322