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 } 210 }); 211 } 212 getTelephonyManager()213 private TelephonyManager getTelephonyManager() { 214 return mTelephonyManager; 215 } 216 217 /** 218 * Checks if there are faulty cards. Adds the text depending on the slot of the card 219 * 220 * @param text: current carrier text based on the sim state 221 * @param carrierNames names order by subscription order 222 * @param subOrderBySlot array containing the sub index for each slot ID 223 * @param noSims: whether a valid sim card is inserted 224 * @return text 225 */ updateCarrierTextWithSimIoError(CharSequence text, CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims)226 private CharSequence updateCarrierTextWithSimIoError(CharSequence text, 227 CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) { 228 final CharSequence carrier = ""; 229 CharSequence carrierTextForSimIOError = getCarrierTextForSimState( 230 TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier); 231 // mSimErrorState has the state of each sim indexed by slotID. 232 for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) { 233 if (!mSimErrorState[index]) { 234 continue; 235 } 236 // In the case when no sim cards are detected but a faulty card is inserted 237 // overwrite the text and only show "Invalid card" 238 if (noSims) { 239 return concatenate(carrierTextForSimIOError, 240 getContext().getText( 241 com.android.internal.R.string.emergency_calls_only), 242 mSeparator); 243 } else if (subOrderBySlot[index] != -1) { 244 int subIndex = subOrderBySlot[index]; 245 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1 246 carrierNames[subIndex] = concatenate(carrierTextForSimIOError, 247 carrierNames[subIndex], 248 mSeparator); 249 } else { 250 // concatenate "Invalid card" when faulty card is inserted in other slot 251 text = concatenate(text, carrierTextForSimIOError, mSeparator); 252 } 253 254 } 255 return text; 256 } 257 258 /** 259 * This may be called internally after retrieving the correct value of {@code mNetworkSupported} 260 * (assumed false to start). In that case, the following happens: 261 * <ul> 262 * <li> If there was a registered callback, and the network is supported, it will register 263 * listeners. 264 * <li> If there was not a registered callback, it will try to remove unregistered listeners 265 * which is a no-op 266 * </ul> 267 * 268 * This call will always be processed in a background thread. 269 */ handleSetListening(CarrierTextCallback callback)270 private void handleSetListening(CarrierTextCallback callback) { 271 if (callback != null) { 272 mCarrierTextCallback = callback; 273 if (mNetworkSupported.get()) { 274 // Keyguard update monitor expects callbacks from main thread 275 mMainExecutor.execute(() -> { 276 mKeyguardUpdateMonitor.registerCallback(mCallback); 277 mWakefulnessLifecycle.addObserver(mWakefulnessObserver); 278 }); 279 mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); 280 } else { 281 // Don't listen and clear out the text when the device isn't a phone. 282 mMainExecutor.execute(() -> callback.updateCarrierInfo( 283 new CarrierTextCallbackInfo("", null, false, null) 284 )); 285 } 286 } else { 287 mCarrierTextCallback = null; 288 mMainExecutor.execute(() -> { 289 mKeyguardUpdateMonitor.removeCallback(mCallback); 290 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); 291 }); 292 mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); 293 } 294 } 295 296 /** 297 * Sets the listening status of this controller. If the callback is null, it is set to 298 * not listening. 299 * 300 * @param callback Callback to provide text updates 301 */ setListening(CarrierTextCallback callback)302 public void setListening(CarrierTextCallback callback) { 303 mBgExecutor.execute(() -> handleSetListening(callback)); 304 } 305 getSubscriptionInfo()306 protected List<SubscriptionInfo> getSubscriptionInfo() { 307 return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(); 308 } 309 updateCarrierText()310 protected void updateCarrierText() { 311 Trace.beginSection("CarrierTextManager#updateCarrierText"); 312 boolean allSimsMissing = true; 313 boolean anySimReadyAndInService = false; 314 CharSequence displayText = null; 315 List<SubscriptionInfo> subs = getSubscriptionInfo(); 316 317 final int numSubs = subs.size(); 318 final int[] subsIds = new int[numSubs]; 319 // This array will contain in position i, the index of subscription in slot ID i. 320 // -1 if no subscription in that slot 321 final int[] subOrderBySlot = new int[mSimSlotsNumber]; 322 for (int i = 0; i < mSimSlotsNumber; i++) { 323 subOrderBySlot[i] = -1; 324 } 325 final CharSequence[] carrierNames = new CharSequence[numSubs]; 326 mLogger.logUpdate(numSubs); 327 328 for (int i = 0; i < numSubs; i++) { 329 int subId = subs.get(i).getSubscriptionId(); 330 carrierNames[i] = ""; 331 subsIds[i] = subId; 332 subOrderBySlot[subs.get(i).getSimSlotIndex()] = i; 333 int simState = mKeyguardUpdateMonitor.getSimState(subId); 334 CharSequence carrierName = subs.get(i).getCarrierName(); 335 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName); 336 mLogger.logUpdateLoopStart(subId, simState, String.valueOf(carrierName)); 337 if (carrierTextForSimState != null) { 338 allSimsMissing = false; 339 carrierNames[i] = carrierTextForSimState; 340 } 341 if (simState == TelephonyManager.SIM_STATE_READY) { 342 Trace.beginSection("WFC check"); 343 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); 344 if (ss != null && ss.getDataRegistrationState() == 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 || mWifiRepository.isWifiConnectedWithValidSsid()) { 349 mLogger.logUpdateWfcCheck(); 350 anySimReadyAndInService = true; 351 } 352 } 353 Trace.endSection(); 354 } 355 } 356 // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY 357 // This condition will also be true always when numSubs == 0 358 if (allSimsMissing && !anySimReadyAndInService) { 359 if (numSubs != 0) { 360 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. 361 // This depends on mPlmn containing the text "Emergency calls only" when the radio 362 // has some connectivity. Otherwise, it should be null or empty and just show 363 // "No SIM card" 364 // Grab the first subscripton, because they all should contain the emergency text, 365 // described above. 366 displayText = makeCarrierStringOnEmergencyCapable( 367 getMissingSimMessage(), subs.get(0).getCarrierName()); 368 } else { 369 // We don't have a SubscriptionInfo to get the emergency calls only from. 370 // Grab it from the old sticky broadcast if possible instead. We can use it 371 // here because no subscriptions are active, so we don't have 372 // to worry about MSIM clashing. 373 CharSequence text = 374 getContext().getText(com.android.internal.R.string.emergency_calls_only); 375 Intent i = getContext().registerReceiver(null, 376 new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)); 377 if (i != null) { 378 String spn = ""; 379 String plmn = ""; 380 if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) { 381 spn = i.getStringExtra(TelephonyManager.EXTRA_SPN); 382 } 383 if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) { 384 plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN); 385 } 386 mLogger.logUpdateFromStickyBroadcast(plmn, spn); 387 if (Objects.equals(plmn, spn)) { 388 text = plmn; 389 } else { 390 text = concatenate(plmn, spn, mSeparator); 391 } 392 } 393 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text); 394 } 395 } 396 397 if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames); 398 399 displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot, 400 allSimsMissing); 401 402 boolean airplaneMode = false; 403 // APM (airplane mode) != no carrier state. There are carrier services 404 // (e.g. WFC = Wi-Fi calling) which may operate in APM. 405 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) { 406 displayText = getAirplaneModeMessage(); 407 airplaneMode = true; 408 } 409 410 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo( 411 displayText, 412 carrierNames, 413 !allSimsMissing, 414 subsIds, 415 airplaneMode); 416 mLogger.logCallbackSentFromUpdate(info); 417 postToCallback(info); 418 Trace.endSection(); 419 } 420 421 @VisibleForTesting postToCallback(CarrierTextCallbackInfo info)422 protected void postToCallback(CarrierTextCallbackInfo info) { 423 final CarrierTextCallback callback = mCarrierTextCallback; 424 if (callback != null) { 425 mMainExecutor.execute(() -> 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(int simState, CharSequence text)449 private CharSequence getCarrierTextForSimState(int simState, CharSequence text) { 450 CharSequence carrierText = null; 451 CarrierTextManager.StatusMode status = getStatusForIccState(simState); 452 switch (status) { 453 case Normal: 454 carrierText = text; 455 break; 456 457 case SimNotReady: 458 // Null is reserved for denoting missing, in this case we have nothing to display. 459 carrierText = ""; // nothing to display yet. 460 break; 461 462 case NetworkLocked: 463 carrierText = makeCarrierStringOnEmergencyCapable( 464 mContext.getText(R.string.keyguard_network_locked_message), text); 465 break; 466 467 case SimMissing: 468 carrierText = null; 469 break; 470 471 case SimPermDisabled: 472 carrierText = makeCarrierStringOnEmergencyCapable( 473 getContext().getText( 474 R.string.keyguard_permanent_disabled_sim_message_short), 475 text); 476 break; 477 478 case SimMissingLocked: 479 carrierText = null; 480 break; 481 482 case SimLocked: 483 carrierText = makeCarrierStringOnLocked( 484 getContext().getText(R.string.keyguard_sim_locked_message), 485 text); 486 break; 487 488 case SimPukLocked: 489 carrierText = makeCarrierStringOnLocked( 490 getContext().getText(R.string.keyguard_sim_puk_locked_message), 491 text); 492 break; 493 case SimIoError: 494 carrierText = makeCarrierStringOnEmergencyCapable( 495 getContext().getText(R.string.keyguard_sim_error_message_short), 496 text); 497 break; 498 case SimRestricted: // fall through 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 */ 541 @VisibleForTesting getStatusForIccState(int simState)542 protected CarrierTextManager.StatusMode getStatusForIccState(int simState) { 543 if (!mKeyguardUpdateMonitor.isDeviceProvisioned() 544 && (simState == TelephonyManager.SIM_STATE_ABSENT 545 || simState == TelephonyManager.SIM_STATE_PERM_DISABLED)) { 546 return CarrierTextManager.StatusMode.SimMissingLocked; 547 } 548 549 switch (simState) { 550 case TelephonyManager.SIM_STATE_ABSENT: 551 return CarrierTextManager.StatusMode.SimMissing; 552 case TelephonyManager.SIM_STATE_NETWORK_LOCKED: 553 return CarrierTextManager.StatusMode.NetworkLocked; 554 case TelephonyManager.SIM_STATE_NOT_READY: 555 return CarrierTextManager.StatusMode.SimNotReady; 556 case TelephonyManager.SIM_STATE_PIN_REQUIRED: 557 return CarrierTextManager.StatusMode.SimLocked; 558 case TelephonyManager.SIM_STATE_PUK_REQUIRED: 559 return CarrierTextManager.StatusMode.SimPukLocked; 560 case TelephonyManager.SIM_STATE_READY: 561 return CarrierTextManager.StatusMode.Normal; 562 case TelephonyManager.SIM_STATE_PERM_DISABLED: 563 return CarrierTextManager.StatusMode.SimPermDisabled; 564 case TelephonyManager.SIM_STATE_UNKNOWN: 565 return CarrierTextManager.StatusMode.SimUnknown; 566 case TelephonyManager.SIM_STATE_CARD_IO_ERROR: 567 return CarrierTextManager.StatusMode.SimIoError; 568 case TelephonyManager.SIM_STATE_CARD_RESTRICTED: 569 return CarrierTextManager.StatusMode.SimRestricted; 570 } 571 return CarrierTextManager.StatusMode.SimUnknown; 572 } 573 concatenate(CharSequence plmn, CharSequence spn, CharSequence separator)574 private static CharSequence concatenate(CharSequence plmn, CharSequence spn, 575 CharSequence separator) { 576 final boolean plmnValid = !TextUtils.isEmpty(plmn); 577 final boolean spnValid = !TextUtils.isEmpty(spn); 578 if (plmnValid && spnValid) { 579 return new StringBuilder().append(plmn).append(separator).append(spn).toString(); 580 } else if (plmnValid) { 581 return plmn; 582 } else if (spnValid) { 583 return spn; 584 } else { 585 return ""; 586 } 587 } 588 589 /** 590 * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra 591 * separator added so there are no extra separators that are not needed. 592 */ joinNotEmpty(CharSequence separator, CharSequence[] sequences)593 private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) { 594 int length = sequences.length; 595 if (length == 0) return ""; 596 StringBuilder sb = new StringBuilder(); 597 for (int i = 0; i < length; i++) { 598 if (!TextUtils.isEmpty(sequences[i])) { 599 if (!TextUtils.isEmpty(sb)) { 600 sb.append(separator); 601 } 602 sb.append(sequences[i]); 603 } 604 } 605 return sb.toString(); 606 } 607 append(List<CharSequence> list, CharSequence string)608 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) { 609 if (!TextUtils.isEmpty(string)) { 610 list.add(string); 611 } 612 return list; 613 } 614 getCarrierHelpTextForSimState(int simState, String plmn, String spn)615 private CharSequence getCarrierHelpTextForSimState(int simState, 616 String plmn, String spn) { 617 int carrierHelpTextId = 0; 618 CarrierTextManager.StatusMode status = getStatusForIccState(simState); 619 switch (status) { 620 case NetworkLocked: 621 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled; 622 break; 623 624 case SimMissing: 625 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long; 626 break; 627 628 case SimPermDisabled: 629 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions; 630 break; 631 632 case SimMissingLocked: 633 carrierHelpTextId = R.string.keyguard_missing_sim_instructions; 634 break; 635 636 case Normal: 637 case SimLocked: 638 case SimPukLocked: 639 break; 640 } 641 642 return mContext.getText(carrierHelpTextId); 643 } 644 645 /** Injectable Buildeer for {@#link CarrierTextManager}. */ 646 public static class Builder { 647 private final Context mContext; 648 private final String mSeparator; 649 private final WifiRepository mWifiRepository; 650 private final TelephonyManager mTelephonyManager; 651 private final TelephonyListenerManager mTelephonyListenerManager; 652 private final WakefulnessLifecycle mWakefulnessLifecycle; 653 private final Executor mMainExecutor; 654 private final Executor mBgExecutor; 655 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 656 private final CarrierTextManagerLogger mLogger; 657 private boolean mShowAirplaneMode; 658 private boolean mShowMissingSim; 659 private String mDebugLocation; 660 661 @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)662 public Builder( 663 Context context, 664 @Main Resources resources, 665 @Nullable WifiRepository wifiRepository, 666 TelephonyManager telephonyManager, 667 TelephonyListenerManager telephonyListenerManager, 668 WakefulnessLifecycle wakefulnessLifecycle, 669 @Main Executor mainExecutor, 670 @Background Executor bgExecutor, 671 KeyguardUpdateMonitor keyguardUpdateMonitor, 672 CarrierTextManagerLogger logger) { 673 mContext = context; 674 mSeparator = resources.getString( 675 com.android.internal.R.string.kg_text_message_separator); 676 mWifiRepository = wifiRepository; 677 mTelephonyManager = telephonyManager; 678 mTelephonyListenerManager = telephonyListenerManager; 679 mWakefulnessLifecycle = wakefulnessLifecycle; 680 mMainExecutor = mainExecutor; 681 mBgExecutor = bgExecutor; 682 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 683 mLogger = logger; 684 } 685 686 /** */ setShowAirplaneMode(boolean showAirplaneMode)687 public Builder setShowAirplaneMode(boolean showAirplaneMode) { 688 mShowAirplaneMode = showAirplaneMode; 689 return this; 690 } 691 692 /** */ setShowMissingSim(boolean showMissingSim)693 public Builder setShowMissingSim(boolean showMissingSim) { 694 mShowMissingSim = showMissingSim; 695 return this; 696 } 697 698 /** 699 * To help disambiguate logs, set a location to be used in the LogBuffer calls, e.g.: 700 * "keyguard" or "keyguard emergency status bar" 701 */ setDebugLocationString(String debugLocationString)702 public Builder setDebugLocationString(String debugLocationString) { 703 mDebugLocation = debugLocationString; 704 return this; 705 } 706 707 /** Create a CarrierTextManager. */ build()708 public CarrierTextManager build() { 709 mLogger.setLocation(mDebugLocation); 710 return new CarrierTextManager( 711 mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository, 712 mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, 713 mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor, mLogger); 714 } 715 } 716 717 /** 718 * Data structure for passing information to CarrierTextController subscribers 719 */ 720 public static final class CarrierTextCallbackInfo { 721 public final CharSequence carrierText; 722 public final CharSequence[] listOfCarriers; 723 public final boolean anySimReady; 724 public final int[] subscriptionIds; 725 public boolean airplaneMode; 726 727 @VisibleForTesting CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, boolean anySimReady, int[] subscriptionIds)728 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, 729 boolean anySimReady, int[] subscriptionIds) { 730 this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false); 731 } 732 733 @VisibleForTesting CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, boolean anySimReady, int[] subscriptionIds, boolean airplaneMode)734 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, 735 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) { 736 this.carrierText = carrierText; 737 this.listOfCarriers = listOfCarriers; 738 this.anySimReady = anySimReady; 739 this.subscriptionIds = subscriptionIds; 740 this.airplaneMode = airplaneMode; 741 } 742 743 @Override toString()744 public String toString() { 745 return "CarrierTextCallbackInfo{" 746 + "carrierText=" + carrierText 747 + ", listOfCarriers=" + Arrays.toString(listOfCarriers) 748 + ", anySimReady=" + anySimReady 749 + ", subscriptionIds=" + Arrays.toString(subscriptionIds) 750 + ", airplaneMode=" + airplaneMode 751 + '}'; 752 } 753 } 754 755 /** 756 * Callback to communicate to Views 757 */ 758 public interface CarrierTextCallback { 759 /** 760 * Provides updated carrier information. 761 */ updateCarrierInfo(CarrierTextCallbackInfo info)762 default void updateCarrierInfo(CarrierTextCallbackInfo info) {}; 763 764 /** 765 * Notifies the View that the device is going to sleep 766 */ startedGoingToSleep()767 default void startedGoingToSleep() {}; 768 769 /** 770 * Notifies the View that the device finished waking up 771 */ finishedWakingUp()772 default void finishedWakingUp() {}; 773 } 774 } 775