Home | History | Annotate | Download | only in impl
      1 /*
      2  * Copyright (C) 2008 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;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.database.ContentObserver;
     24 import static android.os.BatteryManager.BATTERY_STATUS_FULL;
     25 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
     26 import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
     27 import static android.os.BatteryManager.EXTRA_STATUS;
     28 import static android.os.BatteryManager.EXTRA_PLUGGED;
     29 import static android.os.BatteryManager.EXTRA_LEVEL;
     30 import static android.os.BatteryManager.EXTRA_HEALTH;
     31 import android.media.AudioManager;
     32 import android.os.BatteryManager;
     33 import android.os.Handler;
     34 import android.os.Message;
     35 import android.provider.Settings;
     36 import android.provider.Telephony;
     37 import static android.provider.Telephony.Intents.EXTRA_PLMN;
     38 import static android.provider.Telephony.Intents.EXTRA_SHOW_PLMN;
     39 import static android.provider.Telephony.Intents.EXTRA_SHOW_SPN;
     40 import static android.provider.Telephony.Intents.EXTRA_SPN;
     41 import static android.provider.Telephony.Intents.SPN_STRINGS_UPDATED_ACTION;
     42 
     43 import com.android.internal.telephony.IccCard;
     44 import com.android.internal.telephony.TelephonyIntents;
     45 
     46 import android.telephony.TelephonyManager;
     47 import android.util.Log;
     48 import com.android.internal.R;
     49 import com.google.android.collect.Lists;
     50 
     51 import java.util.ArrayList;
     52 
     53 /**
     54  * Watches for updates that may be interesting to the keyguard, and provides
     55  * the up to date information as well as a registration for callbacks that care
     56  * to be updated.
     57  *
     58  * Note: under time crunch, this has been extended to include some stuff that
     59  * doesn't really belong here.  see {@link #handleBatteryUpdate} where it shutdowns
     60  * the device, and {@link #getFailedAttempts()}, {@link #reportFailedAttempt()}
     61  * and {@link #clearFailedAttempts()}.  Maybe we should rename this 'KeyguardContext'...
     62  */
     63 public class KeyguardUpdateMonitor {
     64 
     65     static private final String TAG = "KeyguardUpdateMonitor";
     66     static private final boolean DEBUG = false;
     67 
     68     /* package */ static final int LOW_BATTERY_THRESHOLD = 20;
     69 
     70     private final Context mContext;
     71 
     72     private IccCard.State mSimState = IccCard.State.READY;
     73 
     74     private boolean mKeyguardBypassEnabled;
     75 
     76     private boolean mDeviceProvisioned;
     77 
     78     private BatteryStatus mBatteryStatus;
     79 
     80     private CharSequence mTelephonyPlmn;
     81     private CharSequence mTelephonySpn;
     82 
     83     private int mFailedAttempts = 0;
     84 
     85     private boolean mClockVisible;
     86 
     87     private Handler mHandler;
     88 
     89     private ArrayList<InfoCallback> mInfoCallbacks = Lists.newArrayList();
     90     private ArrayList<SimStateCallback> mSimStateCallbacks = Lists.newArrayList();
     91     private ContentObserver mContentObserver;
     92     private int mRingMode;
     93     private int mPhoneState;
     94 
     95     // messages for the handler
     96     private static final int MSG_TIME_UPDATE = 301;
     97     private static final int MSG_BATTERY_UPDATE = 302;
     98     private static final int MSG_CARRIER_INFO_UPDATE = 303;
     99     private static final int MSG_SIM_STATE_CHANGE = 304;
    100     private static final int MSG_RINGER_MODE_CHANGED = 305;
    101     private static final int MSG_PHONE_STATE_CHANGED = 306;
    102     private static final int MSG_CLOCK_VISIBILITY_CHANGED = 307;
    103     private static final int MSG_DEVICE_PROVISIONED = 308;
    104 
    105     /**
    106      * When we receive a
    107      * {@link com.android.internal.telephony.TelephonyIntents#ACTION_SIM_STATE_CHANGED} broadcast,
    108      * and then pass a result via our handler to {@link KeyguardUpdateMonitor#handleSimStateChange},
    109      * we need a single object to pass to the handler.  This class helps decode
    110      * the intent and provide a {@link SimCard.State} result.
    111      */
    112     private static class SimArgs {
    113         public final IccCard.State simState;
    114 
    115         SimArgs(IccCard.State state) {
    116             simState = state;
    117         }
    118 
    119         static SimArgs fromIntent(Intent intent) {
    120             IccCard.State state;
    121             if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
    122                 throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED");
    123             }
    124             String stateExtra = intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE);
    125             if (IccCard.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
    126                 final String absentReason = intent
    127                     .getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON);
    128 
    129                 if (IccCard.INTENT_VALUE_ABSENT_ON_PERM_DISABLED.equals(
    130                         absentReason)) {
    131                     state = IccCard.State.PERM_DISABLED;
    132                 } else {
    133                     state = IccCard.State.ABSENT;
    134                 }
    135             } else if (IccCard.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
    136                 state = IccCard.State.READY;
    137             } else if (IccCard.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
    138                 final String lockedReason = intent
    139                         .getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON);
    140                 if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
    141                     state = IccCard.State.PIN_REQUIRED;
    142                 } else if (IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
    143                     state = IccCard.State.PUK_REQUIRED;
    144                 } else {
    145                     state = IccCard.State.UNKNOWN;
    146                 }
    147             } else if (IccCard.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
    148                 state = IccCard.State.NETWORK_LOCKED;
    149             } else {
    150                 state = IccCard.State.UNKNOWN;
    151             }
    152             return new SimArgs(state);
    153         }
    154 
    155         public String toString() {
    156             return simState.toString();
    157         }
    158     }
    159 
    160     private static class BatteryStatus {
    161         public final int status;
    162         public final int level;
    163         public final int plugged;
    164         public final int health;
    165         public BatteryStatus(int status, int level, int plugged, int health) {
    166             this.status = status;
    167             this.level = level;
    168             this.plugged = plugged;
    169             this.health = health;
    170         }
    171 
    172     }
    173 
    174     public KeyguardUpdateMonitor(Context context) {
    175         mContext = context;
    176 
    177         mHandler = new Handler() {
    178             @Override
    179             public void handleMessage(Message msg) {
    180                 switch (msg.what) {
    181                     case MSG_TIME_UPDATE:
    182                         handleTimeUpdate();
    183                         break;
    184                     case MSG_BATTERY_UPDATE:
    185                         handleBatteryUpdate((BatteryStatus) msg.obj);
    186                         break;
    187                     case MSG_CARRIER_INFO_UPDATE:
    188                         handleCarrierInfoUpdate();
    189                         break;
    190                     case MSG_SIM_STATE_CHANGE:
    191                         handleSimStateChange((SimArgs) msg.obj);
    192                         break;
    193                     case MSG_RINGER_MODE_CHANGED:
    194                         handleRingerModeChange(msg.arg1);
    195                         break;
    196                     case MSG_PHONE_STATE_CHANGED:
    197                         handlePhoneStateChanged((String)msg.obj);
    198                         break;
    199                     case MSG_CLOCK_VISIBILITY_CHANGED:
    200                         handleClockVisibilityChanged();
    201                         break;
    202                     case MSG_DEVICE_PROVISIONED:
    203                         handleDeviceProvisioned();
    204                         break;
    205                 }
    206             }
    207         };
    208 
    209         mKeyguardBypassEnabled = context.getResources().getBoolean(
    210                 com.android.internal.R.bool.config_bypass_keyguard_if_slider_open);
    211 
    212         mDeviceProvisioned = Settings.Secure.getInt(
    213                 mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
    214 
    215         // Since device can't be un-provisioned, we only need to register a content observer
    216         // to update mDeviceProvisioned when we are...
    217         if (!mDeviceProvisioned) {
    218             mContentObserver = new ContentObserver(mHandler) {
    219                 @Override
    220                 public void onChange(boolean selfChange) {
    221                     super.onChange(selfChange);
    222                     mDeviceProvisioned = Settings.Secure.getInt(mContext.getContentResolver(),
    223                         Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
    224                     if (mDeviceProvisioned) {
    225                         mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED));
    226                     }
    227                     if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
    228                 }
    229             };
    230 
    231             mContext.getContentResolver().registerContentObserver(
    232                     Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED),
    233                     false, mContentObserver);
    234 
    235             // prevent a race condition between where we check the flag and where we register the
    236             // observer by grabbing the value once again...
    237             boolean provisioned = Settings.Secure.getInt(mContext.getContentResolver(),
    238                 Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
    239             if (provisioned != mDeviceProvisioned) {
    240                 mDeviceProvisioned = provisioned;
    241                 if (mDeviceProvisioned) {
    242                     mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED));
    243                 }
    244             }
    245         }
    246 
    247         // take a guess to start
    248         mSimState = IccCard.State.READY;
    249         mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0);
    250 
    251         mTelephonyPlmn = getDefaultPlmn();
    252 
    253         // setup receiver
    254         final IntentFilter filter = new IntentFilter();
    255         filter.addAction(Intent.ACTION_TIME_TICK);
    256         filter.addAction(Intent.ACTION_TIME_CHANGED);
    257         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
    258         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
    259         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
    260         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
    261         filter.addAction(SPN_STRINGS_UPDATED_ACTION);
    262         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
    263         context.registerReceiver(new BroadcastReceiver() {
    264 
    265             public void onReceive(Context context, Intent intent) {
    266                 final String action = intent.getAction();
    267                 if (DEBUG) Log.d(TAG, "received broadcast " + action);
    268 
    269                 if (Intent.ACTION_TIME_TICK.equals(action)
    270                         || Intent.ACTION_TIME_CHANGED.equals(action)
    271                         || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
    272                     mHandler.sendMessage(mHandler.obtainMessage(MSG_TIME_UPDATE));
    273                 } else if (SPN_STRINGS_UPDATED_ACTION.equals(action)) {
    274                     mTelephonyPlmn = getTelephonyPlmnFrom(intent);
    275                     mTelephonySpn = getTelephonySpnFrom(intent);
    276                     mHandler.sendMessage(mHandler.obtainMessage(MSG_CARRIER_INFO_UPDATE));
    277                 } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
    278                     final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
    279                     final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0);
    280                     final int level = intent.getIntExtra(EXTRA_LEVEL, 0);
    281                     final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
    282                     final Message msg = mHandler.obtainMessage(
    283                             MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health));
    284                     mHandler.sendMessage(msg);
    285                 } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
    286                     mHandler.sendMessage(mHandler.obtainMessage(
    287                             MSG_SIM_STATE_CHANGE, SimArgs.fromIntent(intent)));
    288                 } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
    289                     mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED,
    290                             intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0));
    291                 } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
    292                     String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
    293                     mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state));
    294                 }
    295             }
    296         }, filter);
    297     }
    298 
    299     protected void handleDeviceProvisioned() {
    300         for (int i = 0; i < mInfoCallbacks.size(); i++) {
    301             mInfoCallbacks.get(i).onDeviceProvisioned();
    302         }
    303         if (mContentObserver != null) {
    304             // We don't need the observer anymore...
    305             mContext.getContentResolver().unregisterContentObserver(mContentObserver);
    306             mContentObserver = null;
    307         }
    308     }
    309 
    310     protected void handlePhoneStateChanged(String newState) {
    311         if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")");
    312         if (TelephonyManager.EXTRA_STATE_IDLE.equals(newState)) {
    313             mPhoneState = TelephonyManager.CALL_STATE_IDLE;
    314         } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(newState)) {
    315             mPhoneState = TelephonyManager.CALL_STATE_OFFHOOK;
    316         } else if (TelephonyManager.EXTRA_STATE_RINGING.equals(newState)) {
    317             mPhoneState = TelephonyManager.CALL_STATE_RINGING;
    318         }
    319         for (int i = 0; i < mInfoCallbacks.size(); i++) {
    320             mInfoCallbacks.get(i).onPhoneStateChanged(mPhoneState);
    321         }
    322     }
    323 
    324     protected void handleRingerModeChange(int mode) {
    325         if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")");
    326         mRingMode = mode;
    327         for (int i = 0; i < mInfoCallbacks.size(); i++) {
    328             mInfoCallbacks.get(i).onRingerModeChanged(mode);
    329         }
    330     }
    331 
    332     /**
    333      * Handle {@link #MSG_TIME_UPDATE}
    334      */
    335     private void handleTimeUpdate() {
    336         if (DEBUG) Log.d(TAG, "handleTimeUpdate");
    337         for (int i = 0; i < mInfoCallbacks.size(); i++) {
    338             mInfoCallbacks.get(i).onTimeChanged();
    339         }
    340     }
    341 
    342     /**
    343      * Handle {@link #MSG_BATTERY_UPDATE}
    344      */
    345     private void handleBatteryUpdate(BatteryStatus batteryStatus) {
    346         if (DEBUG) Log.d(TAG, "handleBatteryUpdate");
    347         final boolean batteryUpdateInteresting =
    348                 isBatteryUpdateInteresting(mBatteryStatus, batteryStatus);
    349         mBatteryStatus = batteryStatus;
    350         if (batteryUpdateInteresting) {
    351             for (int i = 0; i < mInfoCallbacks.size(); i++) {
    352                 // TODO: pass BatteryStatus object to onRefreshBatteryInfo() instead...
    353                 mInfoCallbacks.get(i).onRefreshBatteryInfo(
    354                     shouldShowBatteryInfo(),isPluggedIn(batteryStatus), batteryStatus.level);
    355             }
    356         }
    357     }
    358 
    359     /**
    360      * Handle {@link #MSG_CARRIER_INFO_UPDATE}
    361      */
    362     private void handleCarrierInfoUpdate() {
    363         if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn
    364             + ", spn = " + mTelephonySpn);
    365 
    366         for (int i = 0; i < mInfoCallbacks.size(); i++) {
    367             mInfoCallbacks.get(i).onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
    368         }
    369     }
    370 
    371     /**
    372      * Handle {@link #MSG_SIM_STATE_CHANGE}
    373      */
    374     private void handleSimStateChange(SimArgs simArgs) {
    375         final IccCard.State state = simArgs.simState;
    376 
    377         if (DEBUG) {
    378             Log.d(TAG, "handleSimStateChange: intentValue = " + simArgs + " "
    379                     + "state resolved to " + state.toString());
    380         }
    381 
    382         if (state != IccCard.State.UNKNOWN && state != mSimState) {
    383             mSimState = state;
    384             for (int i = 0; i < mSimStateCallbacks.size(); i++) {
    385                 mSimStateCallbacks.get(i).onSimStateChanged(state);
    386             }
    387         }
    388     }
    389 
    390     private void handleClockVisibilityChanged() {
    391         if (DEBUG) Log.d(TAG, "handleClockVisibilityChanged()");
    392         for (int i = 0; i < mInfoCallbacks.size(); i++) {
    393             mInfoCallbacks.get(i).onClockVisibilityChanged();
    394         }
    395     }
    396 
    397     /**
    398      * @param pluggedIn state from {@link android.os.BatteryManager#EXTRA_PLUGGED}
    399      * @return Whether the device is considered "plugged in."
    400      */
    401     private static boolean isPluggedIn(BatteryStatus status) {
    402         return status.plugged == BatteryManager.BATTERY_PLUGGED_AC
    403                 || status.plugged == BatteryManager.BATTERY_PLUGGED_USB;
    404     }
    405 
    406     private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
    407         final boolean nowPluggedIn = isPluggedIn(current);
    408         final boolean wasPluggedIn = isPluggedIn(old);
    409         final boolean stateChangedWhilePluggedIn =
    410             wasPluggedIn == true && nowPluggedIn == true
    411             && (old.status != current.status);
    412 
    413         // change in plug state is always interesting
    414         if (wasPluggedIn != nowPluggedIn || stateChangedWhilePluggedIn) {
    415             return true;
    416         }
    417 
    418         // change in battery level while plugged in
    419         if (nowPluggedIn && old.level != current.level) {
    420             return true;
    421         }
    422 
    423         // change where battery needs charging
    424         if (!nowPluggedIn && isBatteryLow(current) && current.level != old.level) {
    425             return true;
    426         }
    427         return false;
    428     }
    429 
    430     private static boolean isBatteryLow(BatteryStatus status) {
    431         return status.level < LOW_BATTERY_THRESHOLD;
    432     }
    433 
    434     /**
    435      * @param intent The intent with action {@link Telephony.Intents#SPN_STRINGS_UPDATED_ACTION}
    436      * @return The string to use for the plmn, or null if it should not be shown.
    437      */
    438     private CharSequence getTelephonyPlmnFrom(Intent intent) {
    439         if (intent.getBooleanExtra(EXTRA_SHOW_PLMN, false)) {
    440             final String plmn = intent.getStringExtra(EXTRA_PLMN);
    441             if (plmn != null) {
    442                 return plmn;
    443             } else {
    444                 return getDefaultPlmn();
    445             }
    446         }
    447         return null;
    448     }
    449 
    450     /**
    451      * @return The default plmn (no service)
    452      */
    453     private CharSequence getDefaultPlmn() {
    454         return mContext.getResources().getText(
    455                         R.string.lockscreen_carrier_default);
    456     }
    457 
    458     /**
    459      * @param intent The intent with action {@link Telephony.Intents#SPN_STRINGS_UPDATED_ACTION}
    460      * @return The string to use for the plmn, or null if it should not be shown.
    461      */
    462     private CharSequence getTelephonySpnFrom(Intent intent) {
    463         if (intent.getBooleanExtra(EXTRA_SHOW_SPN, false)) {
    464             final String spn = intent.getStringExtra(EXTRA_SPN);
    465             if (spn != null) {
    466                 return spn;
    467             }
    468         }
    469         return null;
    470     }
    471 
    472     /**
    473      * Remove the given observer from being registered from any of the kinds
    474      * of callbacks.
    475      * @param observer The observer to remove (an instance of {@link ConfigurationChangeCallback},
    476      *   {@link InfoCallback} or {@link SimStateCallback}
    477      */
    478     public void removeCallback(Object observer) {
    479         mInfoCallbacks.remove(observer);
    480         mSimStateCallbacks.remove(observer);
    481     }
    482 
    483     /**
    484      * Callback for general information relevant to lock screen.
    485      */
    486     interface InfoCallback {
    487         void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel);
    488         void onTimeChanged();
    489 
    490         /**
    491          * @param plmn The operator name of the registered network.  May be null if it shouldn't
    492          *   be displayed.
    493          * @param spn The service provider name.  May be null if it shouldn't be displayed.
    494          */
    495         void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn);
    496 
    497         /**
    498          * Called when the ringer mode changes.
    499          * @param state the current ringer state, as defined in
    500          * {@link AudioManager#RINGER_MODE_CHANGED_ACTION}
    501          */
    502         void onRingerModeChanged(int state);
    503 
    504         /**
    505          * Called when the phone state changes. String will be one of:
    506          * {@link TelephonyManager#EXTRA_STATE_IDLE}
    507          * {@link TelephonyManager@EXTRA_STATE_RINGING}
    508          * {@link TelephonyManager#EXTRA_STATE_OFFHOOK
    509          */
    510         void onPhoneStateChanged(int phoneState);
    511 
    512         /**
    513          * Called when visibility of lockscreen clock changes, such as when
    514          * obscured by a widget.
    515          */
    516         void onClockVisibilityChanged();
    517 
    518         /**
    519          * Called when the device becomes provisioned
    520          */
    521         void onDeviceProvisioned();
    522     }
    523 
    524     /**
    525      * Callback to notify of sim state change.
    526      */
    527     interface SimStateCallback {
    528         void onSimStateChanged(IccCard.State simState);
    529     }
    530 
    531     /**
    532      * Register to receive notifications about general keyguard information
    533      * (see {@link InfoCallback}.
    534      * @param callback The callback.
    535      */
    536     public void registerInfoCallback(InfoCallback callback) {
    537         if (!mInfoCallbacks.contains(callback)) {
    538             mInfoCallbacks.add(callback);
    539             // Notify listener of the current state
    540             callback.onRefreshBatteryInfo(shouldShowBatteryInfo(),isPluggedIn(mBatteryStatus),
    541                     mBatteryStatus.level);
    542             callback.onTimeChanged();
    543             callback.onRingerModeChanged(mRingMode);
    544             callback.onPhoneStateChanged(mPhoneState);
    545             callback.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
    546             callback.onClockVisibilityChanged();
    547         } else {
    548             if (DEBUG) Log.e(TAG, "Object tried to add another INFO callback",
    549                     new Exception("Whoops"));
    550         }
    551     }
    552 
    553     /**
    554      * Register to be notified of sim state changes.
    555      * @param callback The callback.
    556      */
    557     public void registerSimStateCallback(SimStateCallback callback) {
    558         if (!mSimStateCallbacks.contains(callback)) {
    559             mSimStateCallbacks.add(callback);
    560             // Notify listener of the current state
    561             callback.onSimStateChanged(mSimState);
    562         } else {
    563             if (DEBUG) Log.e(TAG, "Object tried to add another SIM callback",
    564                     new Exception("Whoops"));
    565         }
    566     }
    567 
    568     public void reportClockVisible(boolean visible) {
    569         mClockVisible = visible;
    570         mHandler.obtainMessage(MSG_CLOCK_VISIBILITY_CHANGED).sendToTarget();
    571     }
    572 
    573     public IccCard.State getSimState() {
    574         return mSimState;
    575     }
    576 
    577     /**
    578      * Report that the user successfully entered the SIM PIN or PUK/SIM PIN so we
    579      * have the information earlier than waiting for the intent
    580      * broadcast from the telephony code.
    581      *
    582      * NOTE: Because handleSimStateChange() invokes callbacks immediately without going
    583      * through mHandler, this *must* be called from the UI thread.
    584      */
    585     public void reportSimUnlocked() {
    586         handleSimStateChange(new SimArgs(IccCard.State.READY));
    587     }
    588 
    589     public boolean isKeyguardBypassEnabled() {
    590         return mKeyguardBypassEnabled;
    591     }
    592 
    593     public boolean isDevicePluggedIn() {
    594         return isPluggedIn(mBatteryStatus);
    595     }
    596 
    597     public boolean isDeviceCharged() {
    598         return mBatteryStatus.status == BATTERY_STATUS_FULL
    599                 || mBatteryStatus.level >= 100; // in case particular device doesn't flag it
    600     }
    601 
    602     public int getBatteryLevel() {
    603         return mBatteryStatus.level;
    604     }
    605 
    606     public boolean shouldShowBatteryInfo() {
    607         return isPluggedIn(mBatteryStatus) || isBatteryLow(mBatteryStatus);
    608     }
    609 
    610     public CharSequence getTelephonyPlmn() {
    611         return mTelephonyPlmn;
    612     }
    613 
    614     public CharSequence getTelephonySpn() {
    615         return mTelephonySpn;
    616     }
    617 
    618     /**
    619      * @return Whether the device is provisioned (whether they have gone through
    620      *   the setup wizard)
    621      */
    622     public boolean isDeviceProvisioned() {
    623         return mDeviceProvisioned;
    624     }
    625 
    626     public int getFailedAttempts() {
    627         return mFailedAttempts;
    628     }
    629 
    630     public void clearFailedAttempts() {
    631         mFailedAttempts = 0;
    632     }
    633 
    634     public void reportFailedAttempt() {
    635         mFailedAttempts++;
    636     }
    637 
    638     public boolean isClockVisible() {
    639         return mClockVisible;
    640     }
    641 
    642     public int getPhoneState() {
    643         return mPhoneState;
    644     }
    645 }
    646