KeyguardUpdateMonitor.java revision 28a0767751268601c4b8c208e4f8708ee2e88533
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
17package com.android.internal.policy.impl;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.database.ContentObserver;
24import static android.os.BatteryManager.BATTERY_STATUS_FULL;
25import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
26import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
27import static android.os.BatteryManager.EXTRA_STATUS;
28import static android.os.BatteryManager.EXTRA_PLUGGED;
29import static android.os.BatteryManager.EXTRA_LEVEL;
30import static android.os.BatteryManager.EXTRA_HEALTH;
31import android.media.AudioManager;
32import android.os.BatteryManager;
33import android.os.Handler;
34import android.os.Message;
35import android.provider.Settings;
36import android.provider.Telephony;
37import static android.provider.Telephony.Intents.EXTRA_PLMN;
38import static android.provider.Telephony.Intents.EXTRA_SHOW_PLMN;
39import static android.provider.Telephony.Intents.EXTRA_SHOW_SPN;
40import static android.provider.Telephony.Intents.EXTRA_SPN;
41import static android.provider.Telephony.Intents.SPN_STRINGS_UPDATED_ACTION;
42
43import com.android.internal.telephony.IccCard;
44import com.android.internal.telephony.TelephonyIntents;
45
46import android.telephony.TelephonyManager;
47import android.util.Log;
48import com.android.internal.R;
49import com.google.android.collect.Lists;
50
51import 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 */
63public 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