1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.keyguard;
18 
19 import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
20 import static android.telephony.PhoneStateListener.LISTEN_NONE;
21 
22 import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
23 
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.net.ConnectivityManager;
28 import android.net.wifi.WifiManager;
29 import android.os.Handler;
30 import android.os.SystemProperties;
31 import android.telephony.CarrierConfigManager;
32 import android.telephony.PhoneStateListener;
33 import android.telephony.ServiceState;
34 import android.telephony.SubscriptionInfo;
35 import android.telephony.SubscriptionManager;
36 import android.telephony.TelephonyManager;
37 import android.text.TextUtils;
38 import android.util.Log;
39 
40 import androidx.annotation.VisibleForTesting;
41 
42 import com.android.internal.telephony.IccCardConstants;
43 import com.android.internal.telephony.TelephonyIntents;
44 import com.android.internal.telephony.TelephonyProperties;
45 import com.android.settingslib.WirelessUtils;
46 import com.android.systemui.Dependency;
47 import com.android.systemui.keyguard.WakefulnessLifecycle;
48 
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Objects;
52 
53 /**
54  * Controller that generates text including the carrier names and/or the status of all the SIM
55  * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
56  * separated by a given separator {@link CharSequence}.
57  */
58 public class CarrierTextController {
59     private static final boolean DEBUG = KeyguardConstants.DEBUG;
60     private static final String TAG = "CarrierTextController";
61 
62     private final boolean mIsEmergencyCallCapable;
63     private boolean mTelephonyCapable;
64     private boolean mShowMissingSim;
65     private boolean mShowAirplaneMode;
66     @VisibleForTesting
67     protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
68     private WifiManager mWifiManager;
69     private boolean[] mSimErrorState;
70     private final int mSimSlotsNumber;
71     private CarrierTextCallback mCarrierTextCallback;
72     private Context mContext;
73     private CharSequence mSeparator;
74     private WakefulnessLifecycle mWakefulnessLifecycle;
75     @VisibleForTesting
76     protected boolean mDisplayOpportunisticSubscriptionCarrierText;
77     private final WakefulnessLifecycle.Observer mWakefulnessObserver =
78             new WakefulnessLifecycle.Observer() {
79                 @Override
80                 public void onFinishedWakingUp() {
81                     mCarrierTextCallback.finishedWakingUp();
82                 }
83 
84                 @Override
85                 public void onStartedGoingToSleep() {
86                     mCarrierTextCallback.startedGoingToSleep();
87                 }
88             };
89 
90     @VisibleForTesting
91     protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
92         @Override
93         public void onRefreshCarrierInfo() {
94             if (DEBUG) {
95                 Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
96                         + Boolean.toString(mTelephonyCapable));
97             }
98             updateCarrierText();
99         }
100 
101         @Override
102         public void onTelephonyCapable(boolean capable) {
103             if (DEBUG) {
104                 Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
105                         + Boolean.toString(capable));
106             }
107             mTelephonyCapable = capable;
108             updateCarrierText();
109         }
110 
111         public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
112             if (slotId < 0 || slotId >= mSimSlotsNumber) {
113                 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
114                         + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
115                 return;
116             }
117 
118             if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
119             if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
120                 mSimErrorState[slotId] = true;
121                 updateCarrierText();
122             } else if (mSimErrorState[slotId]) {
123                 mSimErrorState[slotId] = false;
124                 updateCarrierText();
125             }
126         }
127     };
128 
129     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
130     private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
131         @Override
132         public void onActiveDataSubscriptionIdChanged(int subId) {
133             mActiveMobileDataSubscription = subId;
134             if (mKeyguardUpdateMonitor != null) {
135                 updateCarrierText();
136             }
137         }
138     };
139 
140     /**
141      * The status of this lock screen. Primarily used for widgets on LockScreen.
142      */
143     private enum StatusMode {
144         Normal, // Normal case (sim card present, it's not locked)
145         NetworkLocked, // SIM card is 'network locked'.
146         SimMissing, // SIM card is missing.
147         SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
148         SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
149         SimLocked, // SIM card is currently locked
150         SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
151         SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
152         SimIoError, // SIM card is faulty
153         SimUnknown // SIM card is unknown
154     }
155 
156     /**
157      * Controller that provides updates on text with carriers names or SIM status.
158      * Used by {@link CarrierText}.
159      *
160      * @param separator Separator between different parts of the text
161      */
CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode, boolean showMissingSim)162     public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
163             boolean showMissingSim) {
164         mContext = context;
165         mIsEmergencyCallCapable = context.getResources().getBoolean(
166                 com.android.internal.R.bool.config_voice_capable);
167 
168         mShowAirplaneMode = showAirplaneMode;
169         mShowMissingSim = showMissingSim;
170 
171         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
172         mSeparator = separator;
173         mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
174         mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
175                 Context.TELEPHONY_SERVICE)).getPhoneCount();
176         mSimErrorState = new boolean[mSimSlotsNumber];
177         updateDisplayOpportunisticSubscriptionCarrierText(SystemProperties.getBoolean(
178                 TelephonyProperties.DISPLAY_OPPORTUNISTIC_SUBSCRIPTION_CARRIER_TEXT_PROPERTY_NAME,
179                 false));
180     }
181 
182     /**
183      * Checks if there are faulty cards. Adds the text depending on the slot of the card
184      *
185      * @param text:   current carrier text based on the sim state
186      * @param carrierNames names order by subscription order
187      * @param subOrderBySlot array containing the sub index for each slot ID
188      * @param noSims: whether a valid sim card is inserted
189      * @return text
190      */
updateCarrierTextWithSimIoError(CharSequence text, CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims)191     private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
192             CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
193         final CharSequence carrier = "";
194         CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
195                 IccCardConstants.State.CARD_IO_ERROR, carrier);
196         // mSimErrorState has the state of each sim indexed by slotID.
197         for (int index = 0; index < mSimErrorState.length; index++) {
198             if (!mSimErrorState[index]) {
199                 continue;
200             }
201             // In the case when no sim cards are detected but a faulty card is inserted
202             // overwrite the text and only show "Invalid card"
203             if (noSims) {
204                 return concatenate(carrierTextForSimIOError,
205                         getContext().getText(
206                                 com.android.internal.R.string.emergency_calls_only),
207                         mSeparator);
208             } else if (subOrderBySlot[index] != -1) {
209                 int subIndex = subOrderBySlot[index];
210                 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
211                 carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
212                         carrierNames[subIndex],
213                         mSeparator);
214             } else {
215                 // concatenate "Invalid card" when faulty card is inserted in other slot
216                 text = concatenate(text, carrierTextForSimIOError, mSeparator);
217             }
218 
219         }
220         return text;
221     }
222 
223     /**
224      * Sets the listening status of this controller. If the callback is null, it is set to
225      * not listening
226      *
227      * @param callback Callback to provide text updates
228      */
setListening(CarrierTextCallback callback)229     public void setListening(CarrierTextCallback callback) {
230         TelephonyManager telephonyManager = ((TelephonyManager) mContext
231                 .getSystemService(Context.TELEPHONY_SERVICE));
232         if (callback != null) {
233             mCarrierTextCallback = callback;
234             if (ConnectivityManager.from(mContext).isNetworkSupported(
235                     ConnectivityManager.TYPE_MOBILE)) {
236                 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
237                 mKeyguardUpdateMonitor.registerCallback(mCallback);
238                 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
239                 telephonyManager.listen(mPhoneStateListener,
240                         LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
241             } else {
242                 // Don't listen and clear out the text when the device isn't a phone.
243                 mKeyguardUpdateMonitor = null;
244                 callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
245             }
246         } else {
247             mCarrierTextCallback = null;
248             if (mKeyguardUpdateMonitor != null) {
249                 mKeyguardUpdateMonitor.removeCallback(mCallback);
250                 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
251             }
252             telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
253         }
254     }
255 
256     /**
257      * @param subscriptions
258      */
filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions)259     private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
260         if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
261             SubscriptionInfo info1 = subscriptions.get(0);
262             SubscriptionInfo info2 = subscriptions.get(1);
263             if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
264                 // If both subscriptions are primary, show both.
265                 if (!info1.isOpportunistic() && !info2.isOpportunistic()) return;
266 
267                 // If carrier required, always show signal bar of primary subscription.
268                 // Otherwise, show whichever subscription is currently active for Internet.
269                 boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig()
270                         .getBoolean(CarrierConfigManager
271                         .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN);
272                 if (alwaysShowPrimary) {
273                     subscriptions.remove(info1.isOpportunistic() ? info1 : info2);
274                 } else {
275                     subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription
276                             ? info2 : info1);
277                 }
278 
279             }
280         }
281     }
282 
283     /**
284      * updates if opportunistic sub carrier text should be displayed or not
285      *
286      */
287     @VisibleForTesting
updateDisplayOpportunisticSubscriptionCarrierText(boolean isEnable)288     public void updateDisplayOpportunisticSubscriptionCarrierText(boolean isEnable) {
289         mDisplayOpportunisticSubscriptionCarrierText = isEnable;
290     }
291 
getSubscriptionInfo()292     protected List<SubscriptionInfo> getSubscriptionInfo() {
293         List<SubscriptionInfo> subs;
294         if (mDisplayOpportunisticSubscriptionCarrierText) {
295             SubscriptionManager subscriptionManager = ((SubscriptionManager) mContext
296                     .getSystemService(
297                     Context.TELEPHONY_SUBSCRIPTION_SERVICE));
298             subs = subscriptionManager.getActiveSubscriptionInfoList(false);
299             if (subs == null) {
300                 subs = new ArrayList<>();
301             } else {
302                 filterMobileSubscriptionInSameGroup(subs);
303             }
304         } else {
305             subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
306         }
307         return subs;
308     }
309 
updateCarrierText()310     protected void updateCarrierText() {
311         boolean allSimsMissing = true;
312         boolean anySimReadyAndInService = false;
313         CharSequence displayText = null;
314         List<SubscriptionInfo> subs = getSubscriptionInfo();
315 
316         final int numSubs = subs.size();
317         final int[] subsIds = new int[numSubs];
318         // This array will contain in position i, the index of subscription in slot ID i.
319         // -1 if no subscription in that slot
320         final int[] subOrderBySlot = new int[mSimSlotsNumber];
321         for (int i = 0; i < mSimSlotsNumber; i++) {
322             subOrderBySlot[i] = -1;
323         }
324         final CharSequence[] carrierNames = new CharSequence[numSubs];
325         if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
326 
327         for (int i = 0; i < numSubs; i++) {
328             int subId = subs.get(i).getSubscriptionId();
329             carrierNames[i] = "";
330             subsIds[i] = subId;
331             subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
332             IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
333             CharSequence carrierName = subs.get(i).getCarrierName();
334             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
335             if (DEBUG) {
336                 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
337             }
338             if (carrierTextForSimState != null) {
339                 allSimsMissing = false;
340                 carrierNames[i] = carrierTextForSimState;
341             }
342             if (simState == IccCardConstants.State.READY) {
343                 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
344                 if (ss != null && ss.getDataRegState() == 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                             || (mWifiManager.isWifiEnabled()
349                             && mWifiManager.getConnectionInfo() != null
350                             && mWifiManager.getConnectionInfo().getBSSID() != null)) {
351                         if (DEBUG) {
352                             Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
353                         }
354                         anySimReadyAndInService = true;
355                     }
356                 }
357             }
358         }
359         if (allSimsMissing) {
360             if (numSubs != 0) {
361                 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
362                 // This depends on mPlmn containing the text "Emergency calls only" when the radio
363                 // has some connectivity. Otherwise, it should be null or empty and just show
364                 // "No SIM card"
365                 // Grab the first subscripton, because they all should contain the emergency text,
366                 // described above.
367                 displayText = makeCarrierStringOnEmergencyCapable(
368                         getMissingSimMessage(), subs.get(0).getCarrierName());
369             } else {
370                 // We don't have a SubscriptionInfo to get the emergency calls only from.
371                 // Grab it from the old sticky broadcast if possible instead. We can use it
372                 // here because no subscriptions are active, so we don't have
373                 // to worry about MSIM clashing.
374                 CharSequence text =
375                         getContext().getText(com.android.internal.R.string.emergency_calls_only);
376                 Intent i = getContext().registerReceiver(null,
377                         new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
378                 if (i != null) {
379                     String spn = "";
380                     String plmn = "";
381                     if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
382                         spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
383                     }
384                     if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
385                         plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
386                     }
387                     if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
388                     if (Objects.equals(plmn, spn)) {
389                         text = plmn;
390                     } else {
391                         text = concatenate(plmn, spn, mSeparator);
392                     }
393                 }
394                 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
395             }
396         }
397 
398         displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
399                 allSimsMissing);
400         boolean airplaneMode = false;
401         // APM (airplane mode) != no carrier state. There are carrier services
402         // (e.g. WFC = Wi-Fi calling) which may operate in APM.
403         if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
404             displayText = getAirplaneModeMessage();
405             airplaneMode = true;
406         }
407 
408         if (TextUtils.isEmpty(displayText) && !airplaneMode) {
409             displayText = joinNotEmpty(mSeparator, carrierNames);
410         }
411         final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
412                 displayText,
413                 carrierNames,
414                 !allSimsMissing,
415                 subsIds,
416                 airplaneMode);
417         postToCallback(info);
418     }
419 
420     @VisibleForTesting
postToCallback(CarrierTextCallbackInfo info)421     protected void postToCallback(CarrierTextCallbackInfo info) {
422         Handler handler = Dependency.get(Dependency.MAIN_HANDLER);
423         final CarrierTextCallback callback = mCarrierTextCallback;
424         if (callback != null) {
425             handler.post(() -> 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(IccCardConstants.State simState, CharSequence text)449     private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
450             CharSequence text) {
451         CharSequence carrierText = null;
452         CarrierTextController.StatusMode status = getStatusForIccState(simState);
453         switch (status) {
454             case Normal:
455                 carrierText = text;
456                 break;
457 
458             case SimNotReady:
459                 // Null is reserved for denoting missing, in this case we have nothing to display.
460                 carrierText = ""; // nothing to display yet.
461                 break;
462 
463             case NetworkLocked:
464                 carrierText = makeCarrierStringOnEmergencyCapable(
465                         mContext.getText(R.string.keyguard_network_locked_message), text);
466                 break;
467 
468             case SimMissing:
469                 carrierText = null;
470                 break;
471 
472             case SimPermDisabled:
473                 carrierText = makeCarrierStringOnEmergencyCapable(
474                         getContext().getText(
475                                 R.string.keyguard_permanent_disabled_sim_message_short),
476                         text);
477                 break;
478 
479             case SimMissingLocked:
480                 carrierText = null;
481                 break;
482 
483             case SimLocked:
484                 carrierText = makeCarrierStringOnLocked(
485                         getContext().getText(R.string.keyguard_sim_locked_message),
486                         text);
487                 break;
488 
489             case SimPukLocked:
490                 carrierText = makeCarrierStringOnLocked(
491                         getContext().getText(R.string.keyguard_sim_puk_locked_message),
492                         text);
493                 break;
494             case SimIoError:
495                 carrierText = makeCarrierStringOnEmergencyCapable(
496                         getContext().getText(R.string.keyguard_sim_error_message_short),
497                         text);
498                 break;
499             case 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      */
getStatusForIccState(IccCardConstants.State simState)541     private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
542         // Since reading the SIM may take a while, we assume it is present until told otherwise.
543         if (simState == null) {
544             return CarrierTextController.StatusMode.Normal;
545         }
546 
547         final boolean missingAndNotProvisioned =
548                 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
549                         && (simState == IccCardConstants.State.ABSENT
550                         || simState == IccCardConstants.State.PERM_DISABLED);
551 
552         // Assume we're NETWORK_LOCKED if not provisioned
553         simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
554         switch (simState) {
555             case ABSENT:
556                 return CarrierTextController.StatusMode.SimMissing;
557             case NETWORK_LOCKED:
558                 return CarrierTextController.StatusMode.SimMissingLocked;
559             case NOT_READY:
560                 return CarrierTextController.StatusMode.SimNotReady;
561             case PIN_REQUIRED:
562                 return CarrierTextController.StatusMode.SimLocked;
563             case PUK_REQUIRED:
564                 return CarrierTextController.StatusMode.SimPukLocked;
565             case READY:
566                 return CarrierTextController.StatusMode.Normal;
567             case PERM_DISABLED:
568                 return CarrierTextController.StatusMode.SimPermDisabled;
569             case UNKNOWN:
570                 return CarrierTextController.StatusMode.SimUnknown;
571             case CARD_IO_ERROR:
572                 return CarrierTextController.StatusMode.SimIoError;
573         }
574         return CarrierTextController.StatusMode.SimUnknown;
575     }
576 
concatenate(CharSequence plmn, CharSequence spn, CharSequence separator)577     private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
578             CharSequence separator) {
579         final boolean plmnValid = !TextUtils.isEmpty(plmn);
580         final boolean spnValid = !TextUtils.isEmpty(spn);
581         if (plmnValid && spnValid) {
582             return new StringBuilder().append(plmn).append(separator).append(spn).toString();
583         } else if (plmnValid) {
584             return plmn;
585         } else if (spnValid) {
586             return spn;
587         } else {
588             return "";
589         }
590     }
591 
592     /**
593      * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
594      * separator added so there are no extra separators that are not needed.
595      */
joinNotEmpty(CharSequence separator, CharSequence[] sequences)596     private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
597         int length = sequences.length;
598         if (length == 0) return "";
599         StringBuilder sb = new StringBuilder();
600         for (int i = 0; i < length; i++) {
601             if (!TextUtils.isEmpty(sequences[i])) {
602                 if (!TextUtils.isEmpty(sb)) {
603                     sb.append(separator);
604                 }
605                 sb.append(sequences[i]);
606             }
607         }
608         return sb.toString();
609     }
610 
append(List<CharSequence> list, CharSequence string)611     private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
612         if (!TextUtils.isEmpty(string)) {
613             list.add(string);
614         }
615         return list;
616     }
617 
getCarrierHelpTextForSimState(IccCardConstants.State simState, String plmn, String spn)618     private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
619             String plmn, String spn) {
620         int carrierHelpTextId = 0;
621         CarrierTextController.StatusMode status = getStatusForIccState(simState);
622         switch (status) {
623             case NetworkLocked:
624                 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
625                 break;
626 
627             case SimMissing:
628                 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
629                 break;
630 
631             case SimPermDisabled:
632                 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
633                 break;
634 
635             case SimMissingLocked:
636                 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
637                 break;
638 
639             case Normal:
640             case SimLocked:
641             case SimPukLocked:
642                 break;
643         }
644 
645         return mContext.getText(carrierHelpTextId);
646     }
647 
648     /**
649      * Data structure for passing information to CarrierTextController subscribers
650      */
651     public static final class CarrierTextCallbackInfo {
652         public final CharSequence carrierText;
653         public final CharSequence[] listOfCarriers;
654         public final boolean anySimReady;
655         public final int[] subscriptionIds;
656         public boolean airplaneMode;
657 
658         @VisibleForTesting
CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, boolean anySimReady, int[] subscriptionIds)659         public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
660                 boolean anySimReady, int[] subscriptionIds) {
661             this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
662         }
663 
664         @VisibleForTesting
CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, boolean anySimReady, int[] subscriptionIds, boolean airplaneMode)665         public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
666                 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
667             this.carrierText = carrierText;
668             this.listOfCarriers = listOfCarriers;
669             this.anySimReady = anySimReady;
670             this.subscriptionIds = subscriptionIds;
671             this.airplaneMode = airplaneMode;
672         }
673     }
674 
675     /**
676      * Callback to communicate to Views
677      */
678     public interface CarrierTextCallback {
679         /**
680          * Provides updated carrier information.
681          */
updateCarrierInfo(CarrierTextCallbackInfo info)682         default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
683 
684         /**
685          * Notifies the View that the device is going to sleep
686          */
startedGoingToSleep()687         default void startedGoingToSleep() {};
688 
689         /**
690          * Notifies the View that the device finished waking up
691          */
finishedWakingUp()692         default void finishedWakingUp() {};
693     }
694 }
695