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 public 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