CarrierText.java revision de3260b5bf0a1a2b3b23d4634400e1ea56f10ee4
1/*
2 * Copyright (C) 2012 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.keyguard;
18
19import java.util.List;
20import java.util.Locale;
21
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.res.TypedArray;
26import android.net.ConnectivityManager;
27import android.telephony.SubscriptionInfo;
28import android.telephony.SubscriptionManager;
29import android.text.TextUtils;
30import android.text.method.SingleLineTransformationMethod;
31import android.util.AttributeSet;
32import android.util.Log;
33import android.view.View;
34import android.widget.TextView;
35
36import com.android.internal.telephony.IccCardConstants;
37import com.android.internal.telephony.IccCardConstants.State;
38import com.android.internal.telephony.TelephonyIntents;
39import com.android.internal.widget.LockPatternUtils;
40
41public class CarrierText extends TextView {
42    private static final boolean DEBUG = KeyguardConstants.DEBUG;
43    private static final String TAG = "CarrierText";
44
45    private static CharSequence mSeparator;
46
47    private LockPatternUtils mLockPatternUtils;
48    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
49
50    private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
51        @Override
52        public void onRefreshCarrierInfo() {
53            updateCarrierText();
54        }
55
56        public void onScreenTurnedOff(int why) {
57            setSelected(false);
58        };
59
60        public void onScreenTurnedOn() {
61            setSelected(true);
62        };
63    };
64    /**
65     * The status of this lock screen. Primarily used for widgets on LockScreen.
66     */
67    private static enum StatusMode {
68        Normal, // Normal case (sim card present, it's not locked)
69        NetworkLocked, // SIM card is 'network locked'.
70        SimMissing, // SIM card is missing.
71        SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
72        SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
73        SimLocked, // SIM card is currently locked
74        SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
75        SimNotReady; // SIM is not ready yet. May never be on devices w/o a SIM.
76    }
77
78    public CarrierText(Context context) {
79        this(context, null);
80    }
81
82    public CarrierText(Context context, AttributeSet attrs) {
83        super(context, attrs);
84        mLockPatternUtils = new LockPatternUtils(mContext);
85        boolean useAllCaps;
86        TypedArray a = context.getTheme().obtainStyledAttributes(
87                attrs, R.styleable.CarrierText, 0, 0);
88        try {
89            useAllCaps = a.getBoolean(R.styleable.CarrierText_allCaps, false);
90        } finally {
91            a.recycle();
92        }
93        setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
94    }
95
96    protected void updateCarrierText() {
97        boolean allSimsMissing = true;
98        CharSequence displayText = null;
99
100        List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
101        final int N = subs.size();
102        if (DEBUG) Log.d(TAG, "updateCarrierText(): " + N);
103        for (int i = 0; i < N; i++) {
104            State simState = mKeyguardUpdateMonitor.getSimState(subs.get(i).getSubscriptionId());
105            CharSequence carrierName = subs.get(i).getCarrierName();
106            CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
107            if (DEBUG) Log.d(TAG, "Handling " + simState + " " + carrierName);
108            if (carrierTextForSimState != null) {
109                allSimsMissing = false;
110                displayText = concatenate(displayText, carrierTextForSimState);
111            }
112        }
113        if (allSimsMissing) {
114            if (N != 0) {
115                // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
116                // This depends on mPlmn containing the text "Emergency calls only" when the radio
117                // has some connectivity. Otherwise, it should be null or empty and just show
118                // "No SIM card"
119                // Grab the first subscripton, because they all should contain the emergency text,
120                // described above.
121                displayText =  makeCarrierStringOnEmergencyCapable(
122                        getContext().getText(R.string.keyguard_missing_sim_message_short),
123                        subs.get(0).getCarrierName());
124            } else {
125                // We don't have a SubscriptionInfo to get the emergency calls only from.
126                // Grab it from the old sticky broadcast if possible instead. We can use it
127                // here because no subscriptions are active, so we don't have
128                // to worry about MSIM clashing.
129                CharSequence text =
130                        getContext().getText(com.android.internal.R.string.emergency_calls_only);
131                Intent i = getContext().registerReceiver(null,
132                        new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
133                if (i != null) {
134                    String spn = "";
135                    String plmn = "";
136                    if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
137                        spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
138                    }
139                    if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
140                        plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
141                    }
142                    if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
143                    text = concatenate(plmn, spn);
144                }
145                displayText =  makeCarrierStringOnEmergencyCapable(
146                        getContext().getText(R.string.keyguard_missing_sim_message_short), text);
147            }
148        }
149        setText(displayText);
150    }
151
152    @Override
153    protected void onFinishInflate() {
154        super.onFinishInflate();
155        mSeparator = getResources().getString(
156                com.android.internal.R.string.kg_text_message_separator);
157        final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
158        setSelected(screenOn); // Allow marquee to work.
159    }
160
161    @Override
162    protected void onAttachedToWindow() {
163        super.onAttachedToWindow();
164        if (ConnectivityManager.from(mContext).isNetworkSupported(
165                ConnectivityManager.TYPE_MOBILE)) {
166            mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
167            mKeyguardUpdateMonitor.registerCallback(mCallback);
168        } else {
169            // Don't listen and clear out the text when the device isn't a phone.
170            mKeyguardUpdateMonitor = null;
171            setText("");
172        }
173    }
174
175    @Override
176    protected void onDetachedFromWindow() {
177        super.onDetachedFromWindow();
178        if (mKeyguardUpdateMonitor != null) {
179            mKeyguardUpdateMonitor.removeCallback(mCallback);
180        }
181    }
182
183    /**
184     * Top-level function for creating carrier text. Makes text based on simState, PLMN
185     * and SPN as well as device capabilities, such as being emergency call capable.
186     *
187     * @param simState
188     * @param text
189     * @param spn
190     * @return Carrier text if not in missing state, null otherwise.
191     */
192    private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
193            CharSequence text) {
194        CharSequence carrierText = null;
195        StatusMode status = getStatusForIccState(simState);
196        switch (status) {
197            case Normal:
198                carrierText = text;
199                break;
200
201            case SimNotReady:
202                // Null is reserved for denoting missing, in this case we have nothing to display.
203                carrierText = ""; // nothing to display yet.
204                break;
205
206            case NetworkLocked:
207                carrierText = makeCarrierStringOnEmergencyCapable(
208                        mContext.getText(R.string.keyguard_network_locked_message), text);
209                break;
210
211            case SimMissing:
212                carrierText = null;
213                break;
214
215            case SimPermDisabled:
216                carrierText = getContext().getText(
217                        R.string.keyguard_permanent_disabled_sim_message_short);
218                break;
219
220            case SimMissingLocked:
221                carrierText = null;
222                break;
223
224            case SimLocked:
225                carrierText = makeCarrierStringOnEmergencyCapable(
226                        getContext().getText(R.string.keyguard_sim_locked_message),
227                        text);
228                break;
229
230            case SimPukLocked:
231                carrierText = makeCarrierStringOnEmergencyCapable(
232                        getContext().getText(R.string.keyguard_sim_puk_locked_message),
233                        text);
234                break;
235        }
236
237        return carrierText;
238    }
239
240    /*
241     * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
242     */
243    private CharSequence makeCarrierStringOnEmergencyCapable(
244            CharSequence simMessage, CharSequence emergencyCallMessage) {
245        if (mLockPatternUtils.isEmergencyCallCapable()) {
246            return concatenate(simMessage, emergencyCallMessage);
247        }
248        return simMessage;
249    }
250
251    /**
252     * Determine the current status of the lock screen given the SIM state and other stuff.
253     */
254    private StatusMode getStatusForIccState(IccCardConstants.State simState) {
255        // Since reading the SIM may take a while, we assume it is present until told otherwise.
256        if (simState == null) {
257            return StatusMode.Normal;
258        }
259
260        final boolean missingAndNotProvisioned =
261                !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
262                && (simState == IccCardConstants.State.ABSENT ||
263                        simState == IccCardConstants.State.PERM_DISABLED);
264
265        // Assume we're NETWORK_LOCKED if not provisioned
266        simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
267        switch (simState) {
268            case ABSENT:
269                return StatusMode.SimMissing;
270            case NETWORK_LOCKED:
271                return StatusMode.SimMissingLocked;
272            case NOT_READY:
273                return StatusMode.SimNotReady;
274            case PIN_REQUIRED:
275                return StatusMode.SimLocked;
276            case PUK_REQUIRED:
277                return StatusMode.SimPukLocked;
278            case READY:
279                return StatusMode.Normal;
280            case PERM_DISABLED:
281                return StatusMode.SimPermDisabled;
282            case UNKNOWN:
283                return StatusMode.SimMissing;
284        }
285        return StatusMode.SimMissing;
286    }
287
288    private static CharSequence concatenate(CharSequence plmn, CharSequence spn) {
289        final boolean plmnValid = !TextUtils.isEmpty(plmn);
290        final boolean spnValid = !TextUtils.isEmpty(spn);
291        if (plmnValid && spnValid) {
292            if (plmn.equals(spn)) {
293                return plmn;
294            } else {
295                return new StringBuilder().append(plmn).append(mSeparator).append(spn).toString();
296            }
297        } else if (plmnValid) {
298            return plmn;
299        } else if (spnValid) {
300            return spn;
301        } else {
302            return "";
303        }
304    }
305
306    private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
307            String plmn, String spn) {
308        int carrierHelpTextId = 0;
309        StatusMode status = getStatusForIccState(simState);
310        switch (status) {
311            case NetworkLocked:
312                carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
313                break;
314
315            case SimMissing:
316                carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
317                break;
318
319            case SimPermDisabled:
320                carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
321                break;
322
323            case SimMissingLocked:
324                carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
325                break;
326
327            case Normal:
328            case SimLocked:
329            case SimPukLocked:
330                break;
331        }
332
333        return mContext.getText(carrierHelpTextId);
334    }
335
336    private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
337        private final Locale mLocale;
338        private final boolean mAllCaps;
339
340        public CarrierTextTransformationMethod(Context context, boolean allCaps) {
341            mLocale = context.getResources().getConfiguration().locale;
342            mAllCaps = allCaps;
343        }
344
345        @Override
346        public CharSequence getTransformation(CharSequence source, View view) {
347            source = super.getTransformation(source, view);
348
349            if (mAllCaps && source != null) {
350                source = source.toString().toUpperCase(mLocale);
351            }
352
353            return source;
354        }
355    }
356}
357