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