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