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