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 com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ACTIVE_DATA_SUB_CHANGED; 20 import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_SIM_STATE_CHANGED; 21 import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_TELEPHONY_CAPABLE; 22 import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_REFRESH_CARRIER_INFO; 23 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.PackageManager; 28 import android.content.res.Resources; 29 import android.os.Trace; 30 import android.telephony.ServiceState; 31 import android.telephony.SubscriptionInfo; 32 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; 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.keyguard.logging.CarrierTextManagerLogger; 41 import com.android.settingslib.WirelessUtils; 42 import com.android.systemui.R; 43 import com.android.systemui.dagger.qualifiers.Background; 44 import com.android.systemui.dagger.qualifiers.Main; 45 import com.android.systemui.keyguard.WakefulnessLifecycle; 46 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository; 47 import com.android.systemui.telephony.TelephonyListenerManager; 48 49 import java.util.Arrays; 50 import java.util.List; 51 import java.util.Objects; 52 import java.util.concurrent.Executor; 53 import java.util.concurrent.atomic.AtomicBoolean; 54 55 import javax.inject.Inject; 56 57 /** 58 * Controller that generates text including the carrier names and/or the status of all the SIM 59 * interfaces in the device. Through a callback, the updates can be retrieved either as a list or 60 * separated by a given separator {@link CharSequence}. 61 * 62 * @deprecated use {@link com.android.systemui.statusbar.pipeline.wifi} instead 63 */ 64 @Deprecated 65 public class CarrierTextManager { 66 private static final boolean DEBUG = KeyguardConstants.DEBUG; 67 private static final String TAG = "CarrierTextController"; 68 69 private final boolean mIsEmergencyCallCapable; 70 private final Executor mMainExecutor; 71 private final Executor mBgExecutor; 72 private boolean mTelephonyCapable; 73 private final boolean mShowMissingSim; 74 private final boolean mShowAirplaneMode; 75 private final AtomicBoolean mNetworkSupported = new AtomicBoolean(); 76 @VisibleForTesting 77 protected KeyguardUpdateMonitor mKeyguardUpdateMonitor; 78 private final CarrierTextManagerLogger mLogger; 79 private final WifiRepository mWifiRepository; 80 private final boolean[] mSimErrorState; 81 private final int mSimSlotsNumber; 82 @Nullable // Check for nullability before dispatching 83 private CarrierTextCallback mCarrierTextCallback; 84 private final Context mContext; 85 private final TelephonyManager mTelephonyManager; 86 private final CharSequence mSeparator; 87 private final TelephonyListenerManager mTelephonyListenerManager; 88 private final WakefulnessLifecycle mWakefulnessLifecycle; 89 private final WakefulnessLifecycle.Observer mWakefulnessObserver = 90 new WakefulnessLifecycle.Observer() { 91 @Override 92 public void onFinishedWakingUp() { 93 final CarrierTextCallback callback = mCarrierTextCallback; 94 if (callback != null) callback.finishedWakingUp(); 95 } 96 97 @Override 98 public void onStartedGoingToSleep() { 99 final CarrierTextCallback callback = mCarrierTextCallback; 100 if (callback != null) callback.startedGoingToSleep(); 101 } 102 }; 103 104 @VisibleForTesting 105 protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { 106 @Override 107 public void onRefreshCarrierInfo() { 108 mLogger.logUpdateCarrierTextForReason(REASON_REFRESH_CARRIER_INFO); 109 updateCarrierText(); 110 } 111 112 @Override 113 public void onTelephonyCapable(boolean capable) { 114 mLogger.logUpdateCarrierTextForReason(REASON_ON_TELEPHONY_CAPABLE); 115 mTelephonyCapable = capable; 116 updateCarrierText(); 117 } 118 119 public void onSimStateChanged(int subId, int slotId, int simState) { 120 if (slotId < 0 || slotId >= mSimSlotsNumber) { 121 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId 122 + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable)); 123 return; 124 } 125 126 mLogger.logUpdateCarrierTextForReason(REASON_ON_SIM_STATE_CHANGED); 127 if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) { 128 mSimErrorState[slotId] = true; 129 updateCarrierText(); 130 } else if (mSimErrorState[slotId]) { 131 mSimErrorState[slotId] = false; 132 updateCarrierText(); 133 } 134 } 135 }; 136 137 private final ActiveDataSubscriptionIdListener mPhoneStateListener = 138 new ActiveDataSubscriptionIdListener() { 139 @Override 140 public void onActiveDataSubscriptionIdChanged(int subId) { 141 if (mNetworkSupported.get() && mCarrierTextCallback != null) { 142 mLogger.logUpdateCarrierTextForReason(REASON_ACTIVE_DATA_SUB_CHANGED); 143 updateCarrierText(); 144 } 145 } 146 }; 147 148 /** 149 * The status of this lock screen. Primarily used for widgets on LockScreen. 150 */ 151 @VisibleForTesting 152 protected enum StatusMode { 153 Normal, // Normal case (sim card present, it's not locked) 154 NetworkLocked, // SIM card is 'network locked'. 155 SimMissing, // SIM card is missing. 156 SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access 157 SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times 158 SimLocked, // SIM card is currently locked 159 SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure 160 SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM. 161 SimIoError, // SIM card is faulty 162 SimRestricted, // SIM Card restricted, present but not usable due to carrier restrictions. 163 SimUnknown // SIM card is unknown 164 } 165 166 /** 167 * Controller that provides updates on text with carriers names or SIM status. 168 * Used by {@link CarrierText}. 169 * 170 * @param separator Separator between different parts of the text 171 */ CarrierTextManager( Context context, CharSequence separator, boolean showAirplaneMode, boolean showMissingSim, WifiRepository wifiRepository, TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, WakefulnessLifecycle wakefulnessLifecycle, @Main Executor mainExecutor, @Background Executor bgExecutor, KeyguardUpdateMonitor keyguardUpdateMonitor, CarrierTextManagerLogger logger)172 private CarrierTextManager( 173 Context context, 174 CharSequence separator, 175 boolean showAirplaneMode, 176 boolean showMissingSim, 177 WifiRepository wifiRepository, 178 TelephonyManager telephonyManager, 179 TelephonyListenerManager telephonyListenerManager, 180 WakefulnessLifecycle wakefulnessLifecycle, 181 @Main Executor mainExecutor, 182 @Background Executor bgExecutor, 183 KeyguardUpdateMonitor keyguardUpdateMonitor, 184 CarrierTextManagerLogger logger) { 185 186 mContext = context; 187 mIsEmergencyCallCapable = telephonyManager.isVoiceCapable(); 188 189 mShowAirplaneMode = showAirplaneMode; 190 mShowMissingSim = showMissingSim; 191 mWifiRepository = wifiRepository; 192 mTelephonyManager = telephonyManager; 193 mSeparator = separator; 194 mTelephonyListenerManager = telephonyListenerManager; 195 mWakefulnessLifecycle = wakefulnessLifecycle; 196 mSimSlotsNumber = getTelephonyManager().getSupportedModemCount(); 197 mSimErrorState = new boolean[mSimSlotsNumber]; 198 mMainExecutor = mainExecutor; 199 mBgExecutor = bgExecutor; 200 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 201 mLogger = logger; 202 mBgExecutor.execute(() -> { 203 boolean supported = mContext.getPackageManager() 204 .hasSystemFeature(PackageManager.FEATURE_TELEPHONY); 205 if (supported && mNetworkSupported.compareAndSet(false, supported)) { 206 // This will set/remove the listeners appropriately. Note that it will never double 207 // add the listeners. 208 handleSetListening(mCarrierTextCallback); 209 mainExecutor.execute(() -> { 210 mKeyguardUpdateMonitor.registerCallback(mCallback); 211 }); 212 } 213 }); 214 } 215 getTelephonyManager()216 private TelephonyManager getTelephonyManager() { 217 return mTelephonyManager; 218 } 219 220 /** 221 * Checks if there are faulty cards. Adds the text depending on the slot of the card 222 * 223 * @param text: current carrier text based on the sim state 224 * @param carrierNames names order by subscription order 225 * @param subOrderBySlot array containing the sub index for each slot ID 226 * @param noSims: whether a valid sim card is inserted 227 * @return text 228 */ updateCarrierTextWithSimIoError(CharSequence text, CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims)229 private CharSequence updateCarrierTextWithSimIoError(CharSequence text, 230 CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) { 231 final CharSequence carrier = ""; 232 CharSequence carrierTextForSimIOError = getCarrierTextForSimState( 233 TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier); 234 // mSimErrorState has the state of each sim indexed by slotID. 235 for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) { 236 if (!mSimErrorState[index]) { 237 continue; 238 } 239 // In the case when no sim cards are detected but a faulty card is inserted 240 // overwrite the text and only show "Invalid card" 241 if (noSims) { 242 return concatenate(carrierTextForSimIOError, 243 getContext().getText( 244 com.android.internal.R.string.emergency_calls_only), 245 mSeparator); 246 } else if (subOrderBySlot[index] != -1) { 247 int subIndex = subOrderBySlot[index]; 248 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1 249 carrierNames[subIndex] = concatenate(carrierTextForSimIOError, 250 carrierNames[subIndex], 251 mSeparator); 252 } else { 253 // concatenate "Invalid card" when faulty card is inserted in other slot 254 text = concatenate(text, carrierTextForSimIOError, mSeparator); 255 } 256 257 } 258 return text; 259 } 260 261 /** 262 * This may be called internally after retrieving the correct value of {@code mNetworkSupported} 263 * (assumed false to start). In that case, the following happens: 264 * <ul> 265 * <li> If there was a registered callback, and the network is supported, it will register 266 * listeners. 267 * <li> If there was not a registered callback, it will try to remove unregistered listeners 268 * which is a no-op 269 * </ul> 270 * 271 * This call will always be processed in a background thread. 272 */ handleSetListening(CarrierTextCallback callback)273 private void handleSetListening(CarrierTextCallback callback) { 274 if (callback != null) { 275 mCarrierTextCallback = callback; 276 if (mNetworkSupported.get()) { 277 // Keyguard update monitor expects callbacks from main thread 278 mMainExecutor.execute(() -> { 279 mWakefulnessLifecycle.addObserver(mWakefulnessObserver); 280 }); 281 mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); 282 } else { 283 // Don't listen and clear out the text when the device isn't a phone. 284 mMainExecutor.execute(() -> callback.updateCarrierInfo( 285 new CarrierTextCallbackInfo("", null, false, null) 286 )); 287 } 288 } else { 289 mCarrierTextCallback = null; 290 mMainExecutor.execute(() -> { 291 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); 292 }); 293 mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); 294 } 295 } 296 297 /** 298 * Sets the listening status of this controller. If the callback is null, it is set to 299 * not listening. 300 * 301 * @param callback Callback to provide text updates 302 */ setListening(CarrierTextCallback callback)303 public void setListening(CarrierTextCallback callback) { 304 mBgExecutor.execute(() -> handleSetListening(callback)); 305 } 306 getSubscriptionInfo()307 protected List<SubscriptionInfo> getSubscriptionInfo() { 308 return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(); 309 } 310 updateCarrierText()311 protected void updateCarrierText() { 312 Trace.beginSection("CarrierTextManager#updateCarrierText"); 313 boolean allSimsMissing = true; 314 boolean anySimReadyAndInService = false; 315 CharSequence displayText = null; 316 List<SubscriptionInfo> subs = getSubscriptionInfo(); 317 318 final int numSubs = subs.size(); 319 final int[] subsIds = new int[numSubs]; 320 // This array will contain in position i, the index of subscription in slot ID i. 321 // -1 if no subscription in that slot 322 final int[] subOrderBySlot = new int[mSimSlotsNumber]; 323 for (int i = 0; i < mSimSlotsNumber; i++) { 324 subOrderBySlot[i] = -1; 325 } 326 final CharSequence[] carrierNames = new CharSequence[numSubs]; 327 mLogger.logUpdate(numSubs); 328 329 for (int i = 0; i < numSubs; i++) { 330 int subId = subs.get(i).getSubscriptionId(); 331 carrierNames[i] = ""; 332 subsIds[i] = subId; 333 subOrderBySlot[subs.get(i).getSimSlotIndex()] = i; 334 int simState = mKeyguardUpdateMonitor.getSimState(subId); 335 CharSequence carrierName = subs.get(i).getCarrierName(); 336 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName); 337 mLogger.logUpdateLoopStart(subId, simState, String.valueOf(carrierName)); 338 if (carrierTextForSimState != null) { 339 allSimsMissing = false; 340 carrierNames[i] = carrierTextForSimState; 341 } 342 if (simState == TelephonyManager.SIM_STATE_READY) { 343 Trace.beginSection("WFC check"); 344 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); 345 if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) { 346 // hack for WFC (IWLAN) not turning off immediately once 347 // Wi-Fi is disassociated or disabled 348 if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN 349 || mWifiRepository.isWifiConnectedWithValidSsid()) { 350 mLogger.logUpdateWfcCheck(); 351 anySimReadyAndInService = true; 352 } 353 } 354 Trace.endSection(); 355 } 356 } 357 // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY 358 // This condition will also be true always when numSubs == 0 359 if (allSimsMissing && !anySimReadyAndInService) { 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(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)); 378 if (i != null) { 379 String spn = ""; 380 String plmn = ""; 381 if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) { 382 spn = i.getStringExtra(TelephonyManager.EXTRA_SPN); 383 } 384 if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) { 385 plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN); 386 } 387 mLogger.logUpdateFromStickyBroadcast(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 if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames); 399 400 displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot, 401 allSimsMissing); 402 403 boolean airplaneMode = false; 404 // APM (airplane mode) != no carrier state. There are carrier services 405 // (e.g. WFC = Wi-Fi calling) which may operate in APM. 406 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) { 407 displayText = getAirplaneModeMessage(); 408 airplaneMode = true; 409 } 410 411 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo( 412 displayText, 413 carrierNames, 414 !allSimsMissing, 415 subsIds, 416 airplaneMode); 417 mLogger.logCallbackSentFromUpdate(info); 418 postToCallback(info); 419 Trace.endSection(); 420 } 421 422 @VisibleForTesting postToCallback(CarrierTextCallbackInfo info)423 protected void postToCallback(CarrierTextCallbackInfo info) { 424 final CarrierTextCallback callback = mCarrierTextCallback; 425 if (callback != null) { 426 mMainExecutor.execute(() -> callback.updateCarrierInfo(info)); 427 } 428 } 429 getContext()430 private Context getContext() { 431 return mContext; 432 } 433 getMissingSimMessage()434 private String getMissingSimMessage() { 435 return mShowMissingSim && mTelephonyCapable 436 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : ""; 437 } 438 getAirplaneModeMessage()439 private String getAirplaneModeMessage() { 440 return mShowAirplaneMode 441 ? getContext().getString(R.string.airplane_mode) : ""; 442 } 443 444 /** 445 * Top-level function for creating carrier text. Makes text based on simState, PLMN 446 * and SPN as well as device capabilities, such as being emergency call capable. 447 * 448 * @return Carrier text if not in missing state, null otherwise. 449 */ getCarrierTextForSimState(int simState, CharSequence text)450 private CharSequence getCarrierTextForSimState(int simState, CharSequence text) { 451 CharSequence carrierText = null; 452 CarrierTextManager.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 SimRestricted: // fall through 500 case SimUnknown: 501 carrierText = null; 502 break; 503 } 504 505 return carrierText; 506 } 507 508 /* 509 * Add emergencyCallMessage to carrier string only if phone supports emergency calls. 510 */ makeCarrierStringOnEmergencyCapable( CharSequence simMessage, CharSequence emergencyCallMessage)511 private CharSequence makeCarrierStringOnEmergencyCapable( 512 CharSequence simMessage, CharSequence emergencyCallMessage) { 513 if (mIsEmergencyCallCapable) { 514 return concatenate(simMessage, emergencyCallMessage, mSeparator); 515 } 516 return simMessage; 517 } 518 519 /* 520 * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in 521 * DSDS 522 */ makeCarrierStringOnLocked(CharSequence simMessage, CharSequence carrierName)523 private CharSequence makeCarrierStringOnLocked(CharSequence simMessage, 524 CharSequence carrierName) { 525 final boolean simMessageValid = !TextUtils.isEmpty(simMessage); 526 final boolean carrierNameValid = !TextUtils.isEmpty(carrierName); 527 if (simMessageValid && carrierNameValid) { 528 return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template, 529 carrierName, simMessage); 530 } else if (simMessageValid) { 531 return simMessage; 532 } else if (carrierNameValid) { 533 return carrierName; 534 } else { 535 return ""; 536 } 537 } 538 539 /** 540 * Determine the current status of the lock screen given the SIM state and other stuff. 541 */ 542 @VisibleForTesting getStatusForIccState(int simState)543 protected CarrierTextManager.StatusMode getStatusForIccState(int simState) { 544 if (!mKeyguardUpdateMonitor.isDeviceProvisioned() 545 && (simState == TelephonyManager.SIM_STATE_ABSENT 546 || simState == TelephonyManager.SIM_STATE_PERM_DISABLED)) { 547 return CarrierTextManager.StatusMode.SimMissingLocked; 548 } 549 550 switch (simState) { 551 case TelephonyManager.SIM_STATE_ABSENT: 552 return CarrierTextManager.StatusMode.SimMissing; 553 case TelephonyManager.SIM_STATE_NETWORK_LOCKED: 554 return CarrierTextManager.StatusMode.NetworkLocked; 555 case TelephonyManager.SIM_STATE_NOT_READY: 556 return CarrierTextManager.StatusMode.SimNotReady; 557 case TelephonyManager.SIM_STATE_PIN_REQUIRED: 558 return CarrierTextManager.StatusMode.SimLocked; 559 case TelephonyManager.SIM_STATE_PUK_REQUIRED: 560 return CarrierTextManager.StatusMode.SimPukLocked; 561 case TelephonyManager.SIM_STATE_READY: 562 return CarrierTextManager.StatusMode.Normal; 563 case TelephonyManager.SIM_STATE_PERM_DISABLED: 564 return CarrierTextManager.StatusMode.SimPermDisabled; 565 case TelephonyManager.SIM_STATE_UNKNOWN: 566 return CarrierTextManager.StatusMode.SimUnknown; 567 case TelephonyManager.SIM_STATE_CARD_IO_ERROR: 568 return CarrierTextManager.StatusMode.SimIoError; 569 case TelephonyManager.SIM_STATE_CARD_RESTRICTED: 570 return CarrierTextManager.StatusMode.SimRestricted; 571 } 572 return CarrierTextManager.StatusMode.SimUnknown; 573 } 574 concatenate(CharSequence plmn, CharSequence spn, CharSequence separator)575 private static CharSequence concatenate(CharSequence plmn, CharSequence spn, 576 CharSequence separator) { 577 final boolean plmnValid = !TextUtils.isEmpty(plmn); 578 final boolean spnValid = !TextUtils.isEmpty(spn); 579 if (plmnValid && spnValid) { 580 return new StringBuilder().append(plmn).append(separator).append(spn).toString(); 581 } else if (plmnValid) { 582 return plmn; 583 } else if (spnValid) { 584 return spn; 585 } else { 586 return ""; 587 } 588 } 589 590 /** 591 * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra 592 * separator added so there are no extra separators that are not needed. 593 */ joinNotEmpty(CharSequence separator, CharSequence[] sequences)594 private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) { 595 int length = sequences.length; 596 if (length == 0) return ""; 597 StringBuilder sb = new StringBuilder(); 598 for (int i = 0; i < length; i++) { 599 if (!TextUtils.isEmpty(sequences[i])) { 600 if (!TextUtils.isEmpty(sb)) { 601 sb.append(separator); 602 } 603 sb.append(sequences[i]); 604 } 605 } 606 return sb.toString(); 607 } 608 append(List<CharSequence> list, CharSequence string)609 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) { 610 if (!TextUtils.isEmpty(string)) { 611 list.add(string); 612 } 613 return list; 614 } 615 getCarrierHelpTextForSimState(int simState, String plmn, String spn)616 private CharSequence getCarrierHelpTextForSimState(int simState, 617 String plmn, String spn) { 618 int carrierHelpTextId = 0; 619 CarrierTextManager.StatusMode status = getStatusForIccState(simState); 620 switch (status) { 621 case NetworkLocked: 622 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled; 623 break; 624 625 case SimMissing: 626 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long; 627 break; 628 629 case SimPermDisabled: 630 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions; 631 break; 632 633 case SimMissingLocked: 634 carrierHelpTextId = R.string.keyguard_missing_sim_instructions; 635 break; 636 637 case Normal: 638 case SimLocked: 639 case SimPukLocked: 640 break; 641 } 642 643 return mContext.getText(carrierHelpTextId); 644 } 645 646 /** Injectable Buildeer for {@#link CarrierTextManager}. */ 647 public static class Builder { 648 private final Context mContext; 649 private final String mSeparator; 650 private final WifiRepository mWifiRepository; 651 private final TelephonyManager mTelephonyManager; 652 private final TelephonyListenerManager mTelephonyListenerManager; 653 private final WakefulnessLifecycle mWakefulnessLifecycle; 654 private final Executor mMainExecutor; 655 private final Executor mBgExecutor; 656 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 657 private final CarrierTextManagerLogger mLogger; 658 private boolean mShowAirplaneMode; 659 private boolean mShowMissingSim; 660 private String mDebugLocation; 661 662 @Inject Builder( Context context, @Main Resources resources, @Nullable WifiRepository wifiRepository, TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, WakefulnessLifecycle wakefulnessLifecycle, @Main Executor mainExecutor, @Background Executor bgExecutor, KeyguardUpdateMonitor keyguardUpdateMonitor, CarrierTextManagerLogger logger)663 public Builder( 664 Context context, 665 @Main Resources resources, 666 @Nullable WifiRepository wifiRepository, 667 TelephonyManager telephonyManager, 668 TelephonyListenerManager telephonyListenerManager, 669 WakefulnessLifecycle wakefulnessLifecycle, 670 @Main Executor mainExecutor, 671 @Background Executor bgExecutor, 672 KeyguardUpdateMonitor keyguardUpdateMonitor, 673 CarrierTextManagerLogger logger) { 674 mContext = context; 675 mSeparator = resources.getString( 676 com.android.internal.R.string.kg_text_message_separator); 677 mWifiRepository = wifiRepository; 678 mTelephonyManager = telephonyManager; 679 mTelephonyListenerManager = telephonyListenerManager; 680 mWakefulnessLifecycle = wakefulnessLifecycle; 681 mMainExecutor = mainExecutor; 682 mBgExecutor = bgExecutor; 683 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 684 mLogger = logger; 685 } 686 687 /** */ setShowAirplaneMode(boolean showAirplaneMode)688 public Builder setShowAirplaneMode(boolean showAirplaneMode) { 689 mShowAirplaneMode = showAirplaneMode; 690 return this; 691 } 692 693 /** */ setShowMissingSim(boolean showMissingSim)694 public Builder setShowMissingSim(boolean showMissingSim) { 695 mShowMissingSim = showMissingSim; 696 return this; 697 } 698 699 /** 700 * To help disambiguate logs, set a location to be used in the LogBuffer calls, e.g.: 701 * "keyguard" or "keyguard emergency status bar" 702 */ setDebugLocationString(String debugLocationString)703 public Builder setDebugLocationString(String debugLocationString) { 704 mDebugLocation = debugLocationString; 705 return this; 706 } 707 708 /** Create a CarrierTextManager. */ build()709 public CarrierTextManager build() { 710 mLogger.setLocation(mDebugLocation); 711 return new CarrierTextManager( 712 mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository, 713 mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, 714 mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor, mLogger); 715 } 716 } 717 718 /** 719 * Data structure for passing information to CarrierTextController subscribers 720 */ 721 public static final class CarrierTextCallbackInfo { 722 public final CharSequence carrierText; 723 public final CharSequence[] listOfCarriers; 724 public final boolean anySimReady; 725 public final int[] subscriptionIds; 726 public boolean airplaneMode; 727 728 @VisibleForTesting CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, boolean anySimReady, int[] subscriptionIds)729 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, 730 boolean anySimReady, int[] subscriptionIds) { 731 this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false); 732 } 733 734 @VisibleForTesting CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, boolean anySimReady, int[] subscriptionIds, boolean airplaneMode)735 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, 736 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) { 737 this.carrierText = carrierText; 738 this.listOfCarriers = listOfCarriers; 739 this.anySimReady = anySimReady; 740 this.subscriptionIds = subscriptionIds; 741 this.airplaneMode = airplaneMode; 742 } 743 744 @Override toString()745 public String toString() { 746 return "CarrierTextCallbackInfo{" 747 + "carrierText=" + carrierText 748 + ", listOfCarriers=" + Arrays.toString(listOfCarriers) 749 + ", anySimReady=" + anySimReady 750 + ", subscriptionIds=" + Arrays.toString(subscriptionIds) 751 + ", airplaneMode=" + airplaneMode 752 + '}'; 753 } 754 } 755 756 /** 757 * Callback to communicate to Views 758 */ 759 public interface CarrierTextCallback { 760 /** 761 * Provides updated carrier information. 762 */ updateCarrierInfo(CarrierTextCallbackInfo info)763 default void updateCarrierInfo(CarrierTextCallbackInfo info) {}; 764 765 /** 766 * Notifies the View that the device is going to sleep 767 */ startedGoingToSleep()768 default void startedGoingToSleep() {}; 769 770 /** 771 * Notifies the View that the device finished waking up 772 */ finishedWakingUp()773 default void finishedWakingUp() {}; 774 } 775 } 776