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