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