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