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