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
17package com.android.internal.policy.impl;
18
19import com.android.internal.R;
20import com.android.internal.telephony.IccCard;
21import com.android.internal.telephony.IccCard.State;
22import com.android.internal.widget.DigitalClock;
23import com.android.internal.widget.LockPatternUtils;
24import com.android.internal.widget.TransportControlView;
25import com.android.internal.policy.impl.KeyguardUpdateMonitor.InfoCallbackImpl;
26import com.android.internal.policy.impl.KeyguardUpdateMonitor.SimStateCallback;
27
28import java.util.ArrayList;
29import java.util.Date;
30
31import libcore.util.MutableInt;
32
33import android.content.ContentResolver;
34import android.content.Context;
35import android.provider.Settings;
36import android.text.TextUtils;
37import android.text.format.DateFormat;
38import android.util.Log;
39import android.view.View;
40import android.view.View.OnClickListener;
41import android.widget.Button;
42import android.widget.TextView;
43
44/***
45 * Manages a number of views inside of LockScreen layouts. See below for a list of widgets
46 *
47 */
48class KeyguardStatusViewManager implements OnClickListener {
49    private static final boolean DEBUG = false;
50    private static final String TAG = "KeyguardStatusView";
51
52    public static final int LOCK_ICON = 0; // R.drawable.ic_lock_idle_lock;
53    public static final int ALARM_ICON = R.drawable.ic_lock_idle_alarm;
54    public static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging;
55    public static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery;
56    private static final long INSTRUCTION_RESET_DELAY = 2000; // time until instruction text resets
57
58    private static final int INSTRUCTION_TEXT = 10;
59    private static final int CARRIER_TEXT = 11;
60    private static final int CARRIER_HELP_TEXT = 12;
61    private static final int HELP_MESSAGE_TEXT = 13;
62    private static final int OWNER_INFO = 14;
63    private static final int BATTERY_INFO = 15;
64
65    private StatusMode mStatus;
66    private String mDateFormatString;
67    private TransientTextManager mTransientTextManager;
68
69    // Views that this class controls.
70    // NOTE: These may be null in some LockScreen screens and should protect from NPE
71    private TextView mCarrierView;
72    private TextView mDateView;
73    private TextView mStatus1View;
74    private TextView mOwnerInfoView;
75    private TextView mAlarmStatusView;
76    private TransportControlView mTransportView;
77
78    // Top-level container view for above views
79    private View mContainer;
80
81    // are we showing battery information?
82    private boolean mShowingBatteryInfo = false;
83
84    // last known plugged in state
85    private boolean mPluggedIn = false;
86
87    // last known battery level
88    private int mBatteryLevel = 100;
89
90    // last known SIM state
91    protected State mSimState;
92
93    private LockPatternUtils mLockPatternUtils;
94    private KeyguardUpdateMonitor mUpdateMonitor;
95    private Button mEmergencyCallButton;
96    private boolean mEmergencyButtonEnabledBecauseSimLocked;
97
98    // Shadowed text values
99    private CharSequence mCarrierText;
100    private CharSequence mCarrierHelpText;
101    private String mHelpMessageText;
102    private String mInstructionText;
103    private CharSequence mOwnerInfoText;
104    private boolean mShowingStatus;
105    private KeyguardScreenCallback mCallback;
106    private final boolean mEmergencyCallButtonEnabledInScreen;
107    private CharSequence mPlmn;
108    private CharSequence mSpn;
109    protected int mPhoneState;
110    private DigitalClock mDigitalClock;
111
112    private class TransientTextManager {
113        private TextView mTextView;
114        private class Data {
115            final int icon;
116            final CharSequence text;
117            Data(CharSequence t, int i) {
118                text = t;
119                icon = i;
120            }
121        };
122        private ArrayList<Data> mMessages = new ArrayList<Data>(5);
123
124        TransientTextManager(TextView textView) {
125            mTextView = textView;
126        }
127
128        /* Show given message with icon for up to duration ms. Newer messages override older ones.
129         * The most recent message with the longest duration is shown as messages expire until
130         * nothing is left, in which case the text/icon is defined by a call to
131         * getAltTextMessage() */
132        void post(final CharSequence message, final int icon, long duration) {
133            if (mTextView == null) {
134                return;
135            }
136            mTextView.setText(message);
137            mTextView.setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0);
138            final Data data = new Data(message, icon);
139            mContainer.postDelayed(new Runnable() {
140                public void run() {
141                    mMessages.remove(data);
142                    int last = mMessages.size() - 1;
143                    final CharSequence lastText;
144                    final int lastIcon;
145                    if (last > 0) {
146                        final Data oldData = mMessages.get(last);
147                        lastText = oldData.text;
148                        lastIcon = oldData.icon;
149                    } else {
150                        final MutableInt tmpIcon = new MutableInt(0);
151                        lastText = getAltTextMessage(tmpIcon);
152                        lastIcon = tmpIcon.value;
153                    }
154                    mTextView.setText(lastText);
155                    mTextView.setCompoundDrawablesWithIntrinsicBounds(lastIcon, 0, 0, 0);
156                }
157            }, duration);
158        }
159    };
160
161    /**
162     *
163     * @param view the containing view of all widgets
164     * @param updateMonitor the update monitor to use
165     * @param lockPatternUtils lock pattern util object
166     * @param callback used to invoke emergency dialer
167     * @param emergencyButtonEnabledInScreen whether emergency button is enabled by default
168     */
169    public KeyguardStatusViewManager(View view, KeyguardUpdateMonitor updateMonitor,
170                LockPatternUtils lockPatternUtils, KeyguardScreenCallback callback,
171                boolean emergencyButtonEnabledInScreen) {
172        if (DEBUG) Log.v(TAG, "KeyguardStatusViewManager()");
173        mContainer = view;
174        mDateFormatString = getContext().getString(R.string.abbrev_wday_month_day_no_year);
175        mLockPatternUtils = lockPatternUtils;
176        mUpdateMonitor = updateMonitor;
177        mCallback = callback;
178
179        mCarrierView = (TextView) findViewById(R.id.carrier);
180        mDateView = (TextView) findViewById(R.id.date);
181        mStatus1View = (TextView) findViewById(R.id.status1);
182        mAlarmStatusView = (TextView) findViewById(R.id.alarm_status);
183        mOwnerInfoView = (TextView) findViewById(R.id.propertyOf);
184        mTransportView = (TransportControlView) findViewById(R.id.transport);
185        mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton);
186        mEmergencyCallButtonEnabledInScreen = emergencyButtonEnabledInScreen;
187        mDigitalClock = (DigitalClock) findViewById(R.id.time);
188
189        // Hide transport control view until we know we need to show it.
190        if (mTransportView != null) {
191            mTransportView.setVisibility(View.GONE);
192        }
193
194        if (mEmergencyCallButton != null) {
195            mEmergencyCallButton.setText(R.string.lockscreen_emergency_call);
196            mEmergencyCallButton.setOnClickListener(this);
197            mEmergencyCallButton.setFocusable(false); // touch only!
198        }
199
200        mTransientTextManager = new TransientTextManager(mCarrierView);
201
202        mUpdateMonitor.registerInfoCallback(mInfoCallback);
203        mUpdateMonitor.registerSimStateCallback(mSimStateCallback);
204
205        resetStatusInfo();
206        refreshDate();
207        updateOwnerInfo();
208
209        // Required to get Marquee to work.
210        final View scrollableViews[] = { mCarrierView, mDateView, mStatus1View, mOwnerInfoView,
211                mAlarmStatusView };
212        for (View v : scrollableViews) {
213            if (v != null) {
214                v.setSelected(true);
215            }
216        }
217    }
218
219    private boolean inWidgetMode() {
220        return mTransportView != null && mTransportView.getVisibility() == View.VISIBLE;
221    }
222
223    void setInstructionText(String string) {
224        mInstructionText = string;
225        update(INSTRUCTION_TEXT, string);
226    }
227
228    void setCarrierText(CharSequence string) {
229        mCarrierText = string;
230        update(CARRIER_TEXT, string);
231    }
232
233    void setOwnerInfo(CharSequence string) {
234        mOwnerInfoText = string;
235        update(OWNER_INFO, string);
236    }
237
238    /**
239     * Sets the carrier help text message, if view is present. Carrier help text messages are
240     * typically for help dealing with SIMS and connectivity.
241     *
242     * @param resId resource id of the message
243     */
244    public void setCarrierHelpText(int resId) {
245        mCarrierHelpText = getText(resId);
246        update(CARRIER_HELP_TEXT, mCarrierHelpText);
247    }
248
249    private CharSequence getText(int resId) {
250        return resId == 0 ? null : getContext().getText(resId);
251    }
252
253    /**
254     * Unlock help message.  This is typically for help with unlock widgets, e.g. "wrong password"
255     * or "try again."
256     *
257     * @param textResId
258     * @param lockIcon
259     */
260    public void setHelpMessage(int textResId, int lockIcon) {
261        final CharSequence tmp = getText(textResId);
262        mHelpMessageText = tmp == null ? null : tmp.toString();
263        update(HELP_MESSAGE_TEXT, mHelpMessageText);
264    }
265
266    private void update(int what, CharSequence string) {
267        if (inWidgetMode()) {
268            if (DEBUG) Log.v(TAG, "inWidgetMode() is true");
269            // Use Transient text for messages shown while widget is shown.
270            switch (what) {
271                case INSTRUCTION_TEXT:
272                case CARRIER_HELP_TEXT:
273                case HELP_MESSAGE_TEXT:
274                case BATTERY_INFO:
275                    mTransientTextManager.post(string, 0, INSTRUCTION_RESET_DELAY);
276                    break;
277
278                case OWNER_INFO:
279                case CARRIER_TEXT:
280                default:
281                    if (DEBUG) Log.w(TAG, "Not showing message id " + what + ", str=" + string);
282            }
283        } else {
284            updateStatusLines(mShowingStatus);
285        }
286    }
287
288    public void onPause() {
289        if (DEBUG) Log.v(TAG, "onPause()");
290        mUpdateMonitor.removeCallback(mInfoCallback);
291        mUpdateMonitor.removeCallback(mSimStateCallback);
292    }
293
294    /** {@inheritDoc} */
295    public void onResume() {
296        if (DEBUG) Log.v(TAG, "onResume()");
297
298        // First update the clock, if present.
299        if (mDigitalClock != null) {
300            mDigitalClock.updateTime();
301        }
302
303        mUpdateMonitor.registerInfoCallback(mInfoCallback);
304        mUpdateMonitor.registerSimStateCallback(mSimStateCallback);
305        resetStatusInfo();
306        // Issue the biometric unlock failure message in a centralized place
307        // TODO: we either need to make the Face Unlock multiple failures string a more general
308        // 'biometric unlock' or have each biometric unlock handle this on their own.
309        if (mUpdateMonitor.getMaxBiometricUnlockAttemptsReached()) {
310            setInstructionText(getContext().getString(R.string.faceunlock_multiple_failures));
311        }
312    }
313
314    void resetStatusInfo() {
315        mInstructionText = null;
316        mShowingBatteryInfo = mUpdateMonitor.shouldShowBatteryInfo();
317        mPluggedIn = mUpdateMonitor.isDevicePluggedIn();
318        mBatteryLevel = mUpdateMonitor.getBatteryLevel();
319        updateStatusLines(true);
320    }
321
322    /**
323     * Update the status lines based on these rules:
324     * AlarmStatus: Alarm state always gets it's own line.
325     * Status1 is shared between help, battery status and generic unlock instructions,
326     * prioritized in that order.
327     * @param showStatusLines status lines are shown if true
328     */
329    void updateStatusLines(boolean showStatusLines) {
330        if (DEBUG) Log.v(TAG, "updateStatusLines(" + showStatusLines + ")");
331        mShowingStatus = showStatusLines;
332        updateAlarmInfo();
333        updateOwnerInfo();
334        updateStatus1();
335        updateCarrierText();
336    }
337
338    private void updateAlarmInfo() {
339        if (mAlarmStatusView != null) {
340            String nextAlarm = mLockPatternUtils.getNextAlarm();
341            boolean showAlarm = mShowingStatus && !TextUtils.isEmpty(nextAlarm);
342            mAlarmStatusView.setText(nextAlarm);
343            mAlarmStatusView.setCompoundDrawablesWithIntrinsicBounds(ALARM_ICON, 0, 0, 0);
344            mAlarmStatusView.setVisibility(showAlarm ? View.VISIBLE : View.GONE);
345        }
346    }
347
348    private void updateOwnerInfo() {
349        final ContentResolver res = getContext().getContentResolver();
350        final boolean ownerInfoEnabled = Settings.Secure.getInt(res,
351                Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0;
352        mOwnerInfoText = ownerInfoEnabled ?
353                Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO) : null;
354        if (mOwnerInfoView != null) {
355            mOwnerInfoView.setText(mOwnerInfoText);
356            mOwnerInfoView.setVisibility(TextUtils.isEmpty(mOwnerInfoText) ? View.GONE:View.VISIBLE);
357        }
358    }
359
360    private void updateStatus1() {
361        if (mStatus1View != null) {
362            MutableInt icon = new MutableInt(0);
363            CharSequence string = getPriorityTextMessage(icon);
364            mStatus1View.setText(string);
365            mStatus1View.setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0);
366            mStatus1View.setVisibility(mShowingStatus ? View.VISIBLE : View.INVISIBLE);
367        }
368    }
369
370    private void updateCarrierText() {
371        if (!inWidgetMode() && mCarrierView != null) {
372            mCarrierView.setText(mCarrierText);
373        }
374    }
375
376    private CharSequence getAltTextMessage(MutableInt icon) {
377        // If we have replaced the status area with a single widget, then this code
378        // prioritizes what to show in that space when all transient messages are gone.
379        CharSequence string = null;
380        if (mShowingBatteryInfo) {
381            // Battery status
382            if (mPluggedIn) {
383                // Charging or charged
384                if (mUpdateMonitor.isDeviceCharged()) {
385                    string = getContext().getString(R.string.lockscreen_charged);
386                } else {
387                    string = getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel);
388                }
389                icon.value = CHARGING_ICON;
390            } else if (mBatteryLevel < KeyguardUpdateMonitor.LOW_BATTERY_THRESHOLD) {
391                // Battery is low
392                string = getContext().getString(R.string.lockscreen_low_battery);
393                icon.value = BATTERY_LOW_ICON;
394            }
395        } else {
396            string = mCarrierText;
397        }
398        return string;
399    }
400
401    private CharSequence getPriorityTextMessage(MutableInt icon) {
402        CharSequence string = null;
403        if (!TextUtils.isEmpty(mInstructionText)) {
404            // Instructions only
405            string = mInstructionText;
406            icon.value = LOCK_ICON;
407        } else if (mShowingBatteryInfo) {
408            // Battery status
409            if (mPluggedIn) {
410                // Charging or charged
411                if (mUpdateMonitor.isDeviceCharged()) {
412                    string = getContext().getString(R.string.lockscreen_charged);
413                } else {
414                    string = getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel);
415                }
416                icon.value = CHARGING_ICON;
417            } else if (mBatteryLevel < KeyguardUpdateMonitor.LOW_BATTERY_THRESHOLD) {
418                // Battery is low
419                string = getContext().getString(R.string.lockscreen_low_battery);
420                icon.value = BATTERY_LOW_ICON;
421            }
422        } else if (!inWidgetMode() && mOwnerInfoView == null && mOwnerInfoText != null) {
423            // OwnerInfo shows in status if we don't have a dedicated widget
424            string = mOwnerInfoText;
425        }
426        return string;
427    }
428
429    void refreshDate() {
430        if (mDateView != null) {
431            mDateView.setText(DateFormat.format(mDateFormatString, new Date()));
432        }
433    }
434
435    /**
436     * Determine the current status of the lock screen given the sim state and other stuff.
437     */
438    public StatusMode getStatusForIccState(IccCard.State simState) {
439        // Since reading the SIM may take a while, we assume it is present until told otherwise.
440        if (simState == null) {
441            return StatusMode.Normal;
442        }
443
444        final boolean missingAndNotProvisioned = (!mUpdateMonitor.isDeviceProvisioned()
445                && (simState == IccCard.State.ABSENT || simState == IccCard.State.PERM_DISABLED));
446
447        // Assume we're NETWORK_LOCKED if not provisioned
448        simState = missingAndNotProvisioned ? State.NETWORK_LOCKED : simState;
449        switch (simState) {
450            case ABSENT:
451                return StatusMode.SimMissing;
452            case NETWORK_LOCKED:
453                return StatusMode.SimMissingLocked;
454            case NOT_READY:
455                return StatusMode.SimMissing;
456            case PIN_REQUIRED:
457                return StatusMode.SimLocked;
458            case PUK_REQUIRED:
459                return StatusMode.SimPukLocked;
460            case READY:
461                return StatusMode.Normal;
462            case PERM_DISABLED:
463                return StatusMode.SimPermDisabled;
464            case UNKNOWN:
465                return StatusMode.SimMissing;
466        }
467        return StatusMode.SimMissing;
468    }
469
470    private Context getContext() {
471        return mContainer.getContext();
472    }
473
474    /**
475     * Update carrier text, carrier help and emergency button to match the current status based
476     * on SIM state.
477     *
478     * @param simState
479     */
480    private void updateCarrierStateWithSimStatus(State simState) {
481        if (DEBUG) Log.d(TAG, "updateCarrierTextWithSimStatus(), simState = " + simState);
482
483        CharSequence carrierText = null;
484        int carrierHelpTextId = 0;
485        mEmergencyButtonEnabledBecauseSimLocked = false;
486        mStatus = getStatusForIccState(simState);
487        mSimState = simState;
488        switch (mStatus) {
489            case Normal:
490                carrierText = makeCarierString(mPlmn, mSpn);
491                break;
492
493            case NetworkLocked:
494                carrierText = makeCarrierStringOnEmergencyCapable(
495                        getContext().getText(R.string.lockscreen_network_locked_message),
496                        mPlmn);
497                carrierHelpTextId = R.string.lockscreen_instructions_when_pattern_disabled;
498                break;
499
500            case SimMissing:
501                // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
502                // This depends on mPlmn containing the text "Emergency calls only" when the radio
503                // has some connectivity. Otherwise, it should be null or empty and just show
504                // "No SIM card"
505                carrierText =  makeCarrierStringOnEmergencyCapable(
506                        getContext().getText(R.string.lockscreen_missing_sim_message_short),
507                        mPlmn);
508                carrierHelpTextId = R.string.lockscreen_missing_sim_instructions_long;
509                break;
510
511            case SimPermDisabled:
512                carrierText = getContext().getText(
513                        R.string.lockscreen_permanent_disabled_sim_message_short);
514                carrierHelpTextId = R.string.lockscreen_permanent_disabled_sim_instructions;
515                mEmergencyButtonEnabledBecauseSimLocked = true;
516                break;
517
518            case SimMissingLocked:
519                carrierText =  makeCarrierStringOnEmergencyCapable(
520                        getContext().getText(R.string.lockscreen_missing_sim_message_short),
521                        mPlmn);
522                carrierHelpTextId = R.string.lockscreen_missing_sim_instructions;
523                mEmergencyButtonEnabledBecauseSimLocked = true;
524                break;
525
526            case SimLocked:
527                carrierText = makeCarrierStringOnEmergencyCapable(
528                        getContext().getText(R.string.lockscreen_sim_locked_message),
529                        mPlmn);
530                mEmergencyButtonEnabledBecauseSimLocked = true;
531                break;
532
533            case SimPukLocked:
534                carrierText = makeCarrierStringOnEmergencyCapable(
535                        getContext().getText(R.string.lockscreen_sim_puk_locked_message),
536                        mPlmn);
537                if (!mLockPatternUtils.isPukUnlockScreenEnable()) {
538                    // This means we're showing the PUK unlock screen
539                    mEmergencyButtonEnabledBecauseSimLocked = true;
540                }
541                break;
542        }
543
544        setCarrierText(carrierText);
545        setCarrierHelpText(carrierHelpTextId);
546        updateEmergencyCallButtonState(mPhoneState);
547    }
548
549
550    /*
551     * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
552     */
553    private CharSequence makeCarrierStringOnEmergencyCapable(
554            CharSequence simMessage, CharSequence emergencyCallMessage) {
555        if (mLockPatternUtils.isEmergencyCallCapable()) {
556            return makeCarierString(simMessage, emergencyCallMessage);
557        }
558        return simMessage;
559    }
560
561    private View findViewById(int id) {
562        return mContainer.findViewById(id);
563    }
564
565    /**
566     * The status of this lock screen. Primarily used for widgets on LockScreen.
567     */
568    enum StatusMode {
569        /**
570         * Normal case (sim card present, it's not locked)
571         */
572        Normal(true),
573
574        /**
575         * The sim card is 'network locked'.
576         */
577        NetworkLocked(true),
578
579        /**
580         * The sim card is missing.
581         */
582        SimMissing(false),
583
584        /**
585         * The sim card is missing, and this is the device isn't provisioned, so we don't let
586         * them get past the screen.
587         */
588        SimMissingLocked(false),
589
590        /**
591         * The sim card is PUK locked, meaning they've entered the wrong sim unlock code too many
592         * times.
593         */
594        SimPukLocked(false),
595
596        /**
597         * The sim card is locked.
598         */
599        SimLocked(true),
600
601        /**
602         * The sim card is permanently disabled due to puk unlock failure
603         */
604        SimPermDisabled(false);
605
606        private final boolean mShowStatusLines;
607
608        StatusMode(boolean mShowStatusLines) {
609            this.mShowStatusLines = mShowStatusLines;
610        }
611
612        /**
613         * @return Whether the status lines (battery level and / or next alarm) are shown while
614         *         in this state.  Mostly dictated by whether this is room for them.
615         */
616        public boolean shouldShowStatusLines() {
617            return mShowStatusLines;
618        }
619    }
620
621    private void updateEmergencyCallButtonState(int phoneState) {
622        if (mEmergencyCallButton != null) {
623            boolean enabledBecauseSimLocked =
624                    mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked()
625                    && mEmergencyButtonEnabledBecauseSimLocked;
626            boolean shown = mEmergencyCallButtonEnabledInScreen || enabledBecauseSimLocked;
627            mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton,
628                    phoneState, shown);
629        }
630    }
631
632    private InfoCallbackImpl mInfoCallback = new InfoCallbackImpl() {
633
634        @Override
635        public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn,
636                int batteryLevel) {
637            mShowingBatteryInfo = showBatteryInfo;
638            mPluggedIn = pluggedIn;
639            mBatteryLevel = batteryLevel;
640            final MutableInt tmpIcon = new MutableInt(0);
641            update(BATTERY_INFO, getAltTextMessage(tmpIcon));
642        }
643
644        @Override
645        public void onTimeChanged() {
646            refreshDate();
647        }
648
649        @Override
650        public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
651            mPlmn = plmn;
652            mSpn = spn;
653            updateCarrierStateWithSimStatus(mSimState);
654        }
655
656        @Override
657        public void onPhoneStateChanged(int phoneState) {
658            mPhoneState = phoneState;
659            updateEmergencyCallButtonState(phoneState);
660        }
661
662    };
663
664    private SimStateCallback mSimStateCallback = new SimStateCallback() {
665
666        public void onSimStateChanged(State simState) {
667            updateCarrierStateWithSimStatus(simState);
668        }
669    };
670
671    public void onClick(View v) {
672        if (v == mEmergencyCallButton) {
673            mCallback.takeEmergencyCallAction();
674        }
675    }
676
677    /**
678     * Performs concentenation of PLMN/SPN
679     * @param plmn
680     * @param spn
681     * @return
682     */
683    private static CharSequence makeCarierString(CharSequence plmn, CharSequence spn) {
684        final boolean plmnValid = !TextUtils.isEmpty(plmn);
685        final boolean spnValid = !TextUtils.isEmpty(spn);
686        if (plmnValid && spnValid) {
687            return plmn + "|" + spn;
688        } else if (plmnValid) {
689            return plmn;
690        } else if (spnValid) {
691            return spn;
692        } else {
693            return "";
694        }
695    }
696}
697