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