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