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