1 /* 2 * Copyright (C) 2019 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 static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; 20 import static android.telephony.PhoneStateListener.LISTEN_NONE; 21 22 import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM; 23 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.net.ConnectivityManager; 28 import android.net.wifi.WifiManager; 29 import android.os.Handler; 30 import android.os.SystemProperties; 31 import android.telephony.CarrierConfigManager; 32 import android.telephony.PhoneStateListener; 33 import android.telephony.ServiceState; 34 import android.telephony.SubscriptionInfo; 35 import android.telephony.SubscriptionManager; 36 import android.telephony.TelephonyManager; 37 import android.text.TextUtils; 38 import android.util.Log; 39 40 import androidx.annotation.VisibleForTesting; 41 42 import com.android.internal.telephony.IccCardConstants; 43 import com.android.internal.telephony.TelephonyIntents; 44 import com.android.internal.telephony.TelephonyProperties; 45 import com.android.settingslib.WirelessUtils; 46 import com.android.systemui.Dependency; 47 import com.android.systemui.keyguard.WakefulnessLifecycle; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.Objects; 52 53 /** 54 * Controller that generates text including the carrier names and/or the status of all the SIM 55 * interfaces in the device. Through a callback, the updates can be retrieved either as a list or 56 * separated by a given separator {@link CharSequence}. 57 */ 58 public class CarrierTextController { 59 private static final boolean DEBUG = KeyguardConstants.DEBUG; 60 private static final String TAG = "CarrierTextController"; 61 62 private final boolean mIsEmergencyCallCapable; 63 private boolean mTelephonyCapable; 64 private boolean mShowMissingSim; 65 private boolean mShowAirplaneMode; 66 @VisibleForTesting 67 protected KeyguardUpdateMonitor mKeyguardUpdateMonitor; 68 private WifiManager mWifiManager; 69 private boolean[] mSimErrorState; 70 private final int mSimSlotsNumber; 71 private CarrierTextCallback mCarrierTextCallback; 72 private Context mContext; 73 private CharSequence mSeparator; 74 private WakefulnessLifecycle mWakefulnessLifecycle; 75 @VisibleForTesting 76 protected boolean mDisplayOpportunisticSubscriptionCarrierText; 77 private final WakefulnessLifecycle.Observer mWakefulnessObserver = 78 new WakefulnessLifecycle.Observer() { 79 @Override 80 public void onFinishedWakingUp() { 81 mCarrierTextCallback.finishedWakingUp(); 82 } 83 84 @Override 85 public void onStartedGoingToSleep() { 86 mCarrierTextCallback.startedGoingToSleep(); 87 } 88 }; 89 90 @VisibleForTesting 91 protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { 92 @Override 93 public void onRefreshCarrierInfo() { 94 if (DEBUG) { 95 Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: " 96 + Boolean.toString(mTelephonyCapable)); 97 } 98 updateCarrierText(); 99 } 100 101 @Override 102 public void onTelephonyCapable(boolean capable) { 103 if (DEBUG) { 104 Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: " 105 + Boolean.toString(capable)); 106 } 107 mTelephonyCapable = capable; 108 updateCarrierText(); 109 } 110 111 public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) { 112 if (slotId < 0 || slotId >= mSimSlotsNumber) { 113 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId 114 + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable)); 115 return; 116 } 117 118 if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState)); 119 if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) { 120 mSimErrorState[slotId] = true; 121 updateCarrierText(); 122 } else if (mSimErrorState[slotId]) { 123 mSimErrorState[slotId] = false; 124 updateCarrierText(); 125 } 126 } 127 }; 128 129 private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 130 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 131 @Override 132 public void onActiveDataSubscriptionIdChanged(int subId) { 133 mActiveMobileDataSubscription = subId; 134 if (mKeyguardUpdateMonitor != null) { 135 updateCarrierText(); 136 } 137 } 138 }; 139 140 /** 141 * The status of this lock screen. Primarily used for widgets on LockScreen. 142 */ 143 private enum StatusMode { 144 Normal, // Normal case (sim card present, it's not locked) 145 NetworkLocked, // SIM card is 'network locked'. 146 SimMissing, // SIM card is missing. 147 SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access 148 SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times 149 SimLocked, // SIM card is currently locked 150 SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure 151 SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM. 152 SimIoError, // SIM card is faulty 153 SimUnknown // SIM card is unknown 154 } 155 156 /** 157 * Controller that provides updates on text with carriers names or SIM status. 158 * Used by {@link CarrierText}. 159 * 160 * @param separator Separator between different parts of the text 161 */ CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode, boolean showMissingSim)162 public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode, 163 boolean showMissingSim) { 164 mContext = context; 165 mIsEmergencyCallCapable = context.getResources().getBoolean( 166 com.android.internal.R.bool.config_voice_capable); 167 168 mShowAirplaneMode = showAirplaneMode; 169 mShowMissingSim = showMissingSim; 170 171 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 172 mSeparator = separator; 173 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class); 174 mSimSlotsNumber = ((TelephonyManager) context.getSystemService( 175 Context.TELEPHONY_SERVICE)).getPhoneCount(); 176 mSimErrorState = new boolean[mSimSlotsNumber]; 177 updateDisplayOpportunisticSubscriptionCarrierText(SystemProperties.getBoolean( 178 TelephonyProperties.DISPLAY_OPPORTUNISTIC_SUBSCRIPTION_CARRIER_TEXT_PROPERTY_NAME, 179 false)); 180 } 181 182 /** 183 * Checks if there are faulty cards. Adds the text depending on the slot of the card 184 * 185 * @param text: current carrier text based on the sim state 186 * @param carrierNames names order by subscription order 187 * @param subOrderBySlot array containing the sub index for each slot ID 188 * @param noSims: whether a valid sim card is inserted 189 * @return text 190 */ updateCarrierTextWithSimIoError(CharSequence text, CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims)191 private CharSequence updateCarrierTextWithSimIoError(CharSequence text, 192 CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) { 193 final CharSequence carrier = ""; 194 CharSequence carrierTextForSimIOError = getCarrierTextForSimState( 195 IccCardConstants.State.CARD_IO_ERROR, carrier); 196 // mSimErrorState has the state of each sim indexed by slotID. 197 for (int index = 0; index < mSimErrorState.length; index++) { 198 if (!mSimErrorState[index]) { 199 continue; 200 } 201 // In the case when no sim cards are detected but a faulty card is inserted 202 // overwrite the text and only show "Invalid card" 203 if (noSims) { 204 return concatenate(carrierTextForSimIOError, 205 getContext().getText( 206 com.android.internal.R.string.emergency_calls_only), 207 mSeparator); 208 } else if (subOrderBySlot[index] != -1) { 209 int subIndex = subOrderBySlot[index]; 210 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1 211 carrierNames[subIndex] = concatenate(carrierTextForSimIOError, 212 carrierNames[subIndex], 213 mSeparator); 214 } else { 215 // concatenate "Invalid card" when faulty card is inserted in other slot 216 text = concatenate(text, carrierTextForSimIOError, mSeparator); 217 } 218 219 } 220 return text; 221 } 222 223 /** 224 * Sets the listening status of this controller. If the callback is null, it is set to 225 * not listening 226 * 227 * @param callback Callback to provide text updates 228 */ setListening(CarrierTextCallback callback)229 public void setListening(CarrierTextCallback callback) { 230 TelephonyManager telephonyManager = ((TelephonyManager) mContext 231 .getSystemService(Context.TELEPHONY_SERVICE)); 232 if (callback != null) { 233 mCarrierTextCallback = callback; 234 if (ConnectivityManager.from(mContext).isNetworkSupported( 235 ConnectivityManager.TYPE_MOBILE)) { 236 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 237 mKeyguardUpdateMonitor.registerCallback(mCallback); 238 mWakefulnessLifecycle.addObserver(mWakefulnessObserver); 239 telephonyManager.listen(mPhoneStateListener, 240 LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); 241 } else { 242 // Don't listen and clear out the text when the device isn't a phone. 243 mKeyguardUpdateMonitor = null; 244 callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null)); 245 } 246 } else { 247 mCarrierTextCallback = null; 248 if (mKeyguardUpdateMonitor != null) { 249 mKeyguardUpdateMonitor.removeCallback(mCallback); 250 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); 251 } 252 telephonyManager.listen(mPhoneStateListener, LISTEN_NONE); 253 } 254 } 255 256 /** 257 * @param subscriptions 258 */ filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions)259 private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) { 260 if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) { 261 SubscriptionInfo info1 = subscriptions.get(0); 262 SubscriptionInfo info2 = subscriptions.get(1); 263 if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) { 264 // If both subscriptions are primary, show both. 265 if (!info1.isOpportunistic() && !info2.isOpportunistic()) return; 266 267 // If carrier required, always show signal bar of primary subscription. 268 // Otherwise, show whichever subscription is currently active for Internet. 269 boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig() 270 .getBoolean(CarrierConfigManager 271 .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN); 272 if (alwaysShowPrimary) { 273 subscriptions.remove(info1.isOpportunistic() ? info1 : info2); 274 } else { 275 subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription 276 ? info2 : info1); 277 } 278 279 } 280 } 281 } 282 283 /** 284 * updates if opportunistic sub carrier text should be displayed or not 285 * 286 */ 287 @VisibleForTesting updateDisplayOpportunisticSubscriptionCarrierText(boolean isEnable)288 public void updateDisplayOpportunisticSubscriptionCarrierText(boolean isEnable) { 289 mDisplayOpportunisticSubscriptionCarrierText = isEnable; 290 } 291 getSubscriptionInfo()292 protected List<SubscriptionInfo> getSubscriptionInfo() { 293 List<SubscriptionInfo> subs; 294 if (mDisplayOpportunisticSubscriptionCarrierText) { 295 SubscriptionManager subscriptionManager = ((SubscriptionManager) mContext 296 .getSystemService( 297 Context.TELEPHONY_SUBSCRIPTION_SERVICE)); 298 subs = subscriptionManager.getActiveSubscriptionInfoList(false); 299 if (subs == null) { 300 subs = new ArrayList<>(); 301 } else { 302 filterMobileSubscriptionInSameGroup(subs); 303 } 304 } else { 305 subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false); 306 } 307 return subs; 308 } 309 updateCarrierText()310 protected void updateCarrierText() { 311 boolean allSimsMissing = true; 312 boolean anySimReadyAndInService = false; 313 CharSequence displayText = null; 314 List<SubscriptionInfo> subs = getSubscriptionInfo(); 315 316 final int numSubs = subs.size(); 317 final int[] subsIds = new int[numSubs]; 318 // This array will contain in position i, the index of subscription in slot ID i. 319 // -1 if no subscription in that slot 320 final int[] subOrderBySlot = new int[mSimSlotsNumber]; 321 for (int i = 0; i < mSimSlotsNumber; i++) { 322 subOrderBySlot[i] = -1; 323 } 324 final CharSequence[] carrierNames = new CharSequence[numSubs]; 325 if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs); 326 327 for (int i = 0; i < numSubs; i++) { 328 int subId = subs.get(i).getSubscriptionId(); 329 carrierNames[i] = ""; 330 subsIds[i] = subId; 331 subOrderBySlot[subs.get(i).getSimSlotIndex()] = i; 332 IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId); 333 CharSequence carrierName = subs.get(i).getCarrierName(); 334 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName); 335 if (DEBUG) { 336 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName); 337 } 338 if (carrierTextForSimState != null) { 339 allSimsMissing = false; 340 carrierNames[i] = carrierTextForSimState; 341 } 342 if (simState == IccCardConstants.State.READY) { 343 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); 344 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) { 345 // hack for WFC (IWLAN) not turning off immediately once 346 // Wi-Fi is disassociated or disabled 347 if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN 348 || (mWifiManager.isWifiEnabled() 349 && mWifiManager.getConnectionInfo() != null 350 && mWifiManager.getConnectionInfo().getBSSID() != null)) { 351 if (DEBUG) { 352 Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss); 353 } 354 anySimReadyAndInService = true; 355 } 356 } 357 } 358 } 359 if (allSimsMissing) { 360 if (numSubs != 0) { 361 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. 362 // This depends on mPlmn containing the text "Emergency calls only" when the radio 363 // has some connectivity. Otherwise, it should be null or empty and just show 364 // "No SIM card" 365 // Grab the first subscripton, because they all should contain the emergency text, 366 // described above. 367 displayText = makeCarrierStringOnEmergencyCapable( 368 getMissingSimMessage(), subs.get(0).getCarrierName()); 369 } else { 370 // We don't have a SubscriptionInfo to get the emergency calls only from. 371 // Grab it from the old sticky broadcast if possible instead. We can use it 372 // here because no subscriptions are active, so we don't have 373 // to worry about MSIM clashing. 374 CharSequence text = 375 getContext().getText(com.android.internal.R.string.emergency_calls_only); 376 Intent i = getContext().registerReceiver(null, 377 new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)); 378 if (i != null) { 379 String spn = ""; 380 String plmn = ""; 381 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) { 382 spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN); 383 } 384 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) { 385 plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN); 386 } 387 if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn); 388 if (Objects.equals(plmn, spn)) { 389 text = plmn; 390 } else { 391 text = concatenate(plmn, spn, mSeparator); 392 } 393 } 394 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text); 395 } 396 } 397 398 displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot, 399 allSimsMissing); 400 boolean airplaneMode = false; 401 // APM (airplane mode) != no carrier state. There are carrier services 402 // (e.g. WFC = Wi-Fi calling) which may operate in APM. 403 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) { 404 displayText = getAirplaneModeMessage(); 405 airplaneMode = true; 406 } 407 408 if (TextUtils.isEmpty(displayText) && !airplaneMode) { 409 displayText = joinNotEmpty(mSeparator, carrierNames); 410 } 411 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo( 412 displayText, 413 carrierNames, 414 !allSimsMissing, 415 subsIds, 416 airplaneMode); 417 postToCallback(info); 418 } 419 420 @VisibleForTesting postToCallback(CarrierTextCallbackInfo info)421 protected void postToCallback(CarrierTextCallbackInfo info) { 422 Handler handler = Dependency.get(Dependency.MAIN_HANDLER); 423 final CarrierTextCallback callback = mCarrierTextCallback; 424 if (callback != null) { 425 handler.post(() -> callback.updateCarrierInfo(info)); 426 } 427 } 428 getContext()429 private Context getContext() { 430 return mContext; 431 } 432 getMissingSimMessage()433 private String getMissingSimMessage() { 434 return mShowMissingSim && mTelephonyCapable 435 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : ""; 436 } 437 getAirplaneModeMessage()438 private String getAirplaneModeMessage() { 439 return mShowAirplaneMode 440 ? getContext().getString(R.string.airplane_mode) : ""; 441 } 442 443 /** 444 * Top-level function for creating carrier text. Makes text based on simState, PLMN 445 * and SPN as well as device capabilities, such as being emergency call capable. 446 * 447 * @return Carrier text if not in missing state, null otherwise. 448 */ getCarrierTextForSimState(IccCardConstants.State simState, CharSequence text)449 private CharSequence getCarrierTextForSimState(IccCardConstants.State simState, 450 CharSequence text) { 451 CharSequence carrierText = null; 452 CarrierTextController.StatusMode status = getStatusForIccState(simState); 453 switch (status) { 454 case Normal: 455 carrierText = text; 456 break; 457 458 case SimNotReady: 459 // Null is reserved for denoting missing, in this case we have nothing to display. 460 carrierText = ""; // nothing to display yet. 461 break; 462 463 case NetworkLocked: 464 carrierText = makeCarrierStringOnEmergencyCapable( 465 mContext.getText(R.string.keyguard_network_locked_message), text); 466 break; 467 468 case SimMissing: 469 carrierText = null; 470 break; 471 472 case SimPermDisabled: 473 carrierText = makeCarrierStringOnEmergencyCapable( 474 getContext().getText( 475 R.string.keyguard_permanent_disabled_sim_message_short), 476 text); 477 break; 478 479 case SimMissingLocked: 480 carrierText = null; 481 break; 482 483 case SimLocked: 484 carrierText = makeCarrierStringOnLocked( 485 getContext().getText(R.string.keyguard_sim_locked_message), 486 text); 487 break; 488 489 case SimPukLocked: 490 carrierText = makeCarrierStringOnLocked( 491 getContext().getText(R.string.keyguard_sim_puk_locked_message), 492 text); 493 break; 494 case SimIoError: 495 carrierText = makeCarrierStringOnEmergencyCapable( 496 getContext().getText(R.string.keyguard_sim_error_message_short), 497 text); 498 break; 499 case SimUnknown: 500 carrierText = null; 501 break; 502 } 503 504 return carrierText; 505 } 506 507 /* 508 * Add emergencyCallMessage to carrier string only if phone supports emergency calls. 509 */ makeCarrierStringOnEmergencyCapable( CharSequence simMessage, CharSequence emergencyCallMessage)510 private CharSequence makeCarrierStringOnEmergencyCapable( 511 CharSequence simMessage, CharSequence emergencyCallMessage) { 512 if (mIsEmergencyCallCapable) { 513 return concatenate(simMessage, emergencyCallMessage, mSeparator); 514 } 515 return simMessage; 516 } 517 518 /* 519 * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in 520 * DSDS 521 */ makeCarrierStringOnLocked(CharSequence simMessage, CharSequence carrierName)522 private CharSequence makeCarrierStringOnLocked(CharSequence simMessage, 523 CharSequence carrierName) { 524 final boolean simMessageValid = !TextUtils.isEmpty(simMessage); 525 final boolean carrierNameValid = !TextUtils.isEmpty(carrierName); 526 if (simMessageValid && carrierNameValid) { 527 return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template, 528 carrierName, simMessage); 529 } else if (simMessageValid) { 530 return simMessage; 531 } else if (carrierNameValid) { 532 return carrierName; 533 } else { 534 return ""; 535 } 536 } 537 538 /** 539 * Determine the current status of the lock screen given the SIM state and other stuff. 540 */ getStatusForIccState(IccCardConstants.State simState)541 private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) { 542 // Since reading the SIM may take a while, we assume it is present until told otherwise. 543 if (simState == null) { 544 return CarrierTextController.StatusMode.Normal; 545 } 546 547 final boolean missingAndNotProvisioned = 548 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned() 549 && (simState == IccCardConstants.State.ABSENT 550 || simState == IccCardConstants.State.PERM_DISABLED); 551 552 // Assume we're NETWORK_LOCKED if not provisioned 553 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState; 554 switch (simState) { 555 case ABSENT: 556 return CarrierTextController.StatusMode.SimMissing; 557 case NETWORK_LOCKED: 558 return CarrierTextController.StatusMode.SimMissingLocked; 559 case NOT_READY: 560 return CarrierTextController.StatusMode.SimNotReady; 561 case PIN_REQUIRED: 562 return CarrierTextController.StatusMode.SimLocked; 563 case PUK_REQUIRED: 564 return CarrierTextController.StatusMode.SimPukLocked; 565 case READY: 566 return CarrierTextController.StatusMode.Normal; 567 case PERM_DISABLED: 568 return CarrierTextController.StatusMode.SimPermDisabled; 569 case UNKNOWN: 570 return CarrierTextController.StatusMode.SimUnknown; 571 case CARD_IO_ERROR: 572 return CarrierTextController.StatusMode.SimIoError; 573 } 574 return CarrierTextController.StatusMode.SimUnknown; 575 } 576 concatenate(CharSequence plmn, CharSequence spn, CharSequence separator)577 private static CharSequence concatenate(CharSequence plmn, CharSequence spn, 578 CharSequence separator) { 579 final boolean plmnValid = !TextUtils.isEmpty(plmn); 580 final boolean spnValid = !TextUtils.isEmpty(spn); 581 if (plmnValid && spnValid) { 582 return new StringBuilder().append(plmn).append(separator).append(spn).toString(); 583 } else if (plmnValid) { 584 return plmn; 585 } else if (spnValid) { 586 return spn; 587 } else { 588 return ""; 589 } 590 } 591 592 /** 593 * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra 594 * separator added so there are no extra separators that are not needed. 595 */ joinNotEmpty(CharSequence separator, CharSequence[] sequences)596 private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) { 597 int length = sequences.length; 598 if (length == 0) return ""; 599 StringBuilder sb = new StringBuilder(); 600 for (int i = 0; i < length; i++) { 601 if (!TextUtils.isEmpty(sequences[i])) { 602 if (!TextUtils.isEmpty(sb)) { 603 sb.append(separator); 604 } 605 sb.append(sequences[i]); 606 } 607 } 608 return sb.toString(); 609 } 610 append(List<CharSequence> list, CharSequence string)611 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) { 612 if (!TextUtils.isEmpty(string)) { 613 list.add(string); 614 } 615 return list; 616 } 617 getCarrierHelpTextForSimState(IccCardConstants.State simState, String plmn, String spn)618 private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState, 619 String plmn, String spn) { 620 int carrierHelpTextId = 0; 621 CarrierTextController.StatusMode status = getStatusForIccState(simState); 622 switch (status) { 623 case NetworkLocked: 624 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled; 625 break; 626 627 case SimMissing: 628 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long; 629 break; 630 631 case SimPermDisabled: 632 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions; 633 break; 634 635 case SimMissingLocked: 636 carrierHelpTextId = R.string.keyguard_missing_sim_instructions; 637 break; 638 639 case Normal: 640 case SimLocked: 641 case SimPukLocked: 642 break; 643 } 644 645 return mContext.getText(carrierHelpTextId); 646 } 647 648 /** 649 * Data structure for passing information to CarrierTextController subscribers 650 */ 651 public static final class CarrierTextCallbackInfo { 652 public final CharSequence carrierText; 653 public final CharSequence[] listOfCarriers; 654 public final boolean anySimReady; 655 public final int[] subscriptionIds; 656 public boolean airplaneMode; 657 658 @VisibleForTesting CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, boolean anySimReady, int[] subscriptionIds)659 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, 660 boolean anySimReady, int[] subscriptionIds) { 661 this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false); 662 } 663 664 @VisibleForTesting CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, boolean anySimReady, int[] subscriptionIds, boolean airplaneMode)665 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, 666 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) { 667 this.carrierText = carrierText; 668 this.listOfCarriers = listOfCarriers; 669 this.anySimReady = anySimReady; 670 this.subscriptionIds = subscriptionIds; 671 this.airplaneMode = airplaneMode; 672 } 673 } 674 675 /** 676 * Callback to communicate to Views 677 */ 678 public interface CarrierTextCallback { 679 /** 680 * Provides updated carrier information. 681 */ updateCarrierInfo(CarrierTextCallbackInfo info)682 default void updateCarrierInfo(CarrierTextCallbackInfo info) {}; 683 684 /** 685 * Notifies the View that the device is going to sleep 686 */ startedGoingToSleep()687 default void startedGoingToSleep() {}; 688 689 /** 690 * Notifies the View that the device finished waking up 691 */ finishedWakingUp()692 default void finishedWakingUp() {}; 693 } 694 } 695