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(() -> {
265                     mKeyguardUpdateMonitor.registerCallback(mCallback);
266                     mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
267                 });
268                 mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
269             } else {
270                 // Don't listen and clear out the text when the device isn't a phone.
271                 mMainExecutor.execute(() -> callback.updateCarrierInfo(
272                         new CarrierTextCallbackInfo("", null, false, null)
273                 ));
274             }
275         } else {
276             mCarrierTextCallback = null;
277             mMainExecutor.execute(() -> {
278                 mKeyguardUpdateMonitor.removeCallback(mCallback);
279                 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
280             });
281             mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
282         }
283     }
284 
285     /**
286      * Sets the listening status of this controller. If the callback is null, it is set to
287      * not listening.
288      *
289      * @param callback Callback to provide text updates
290      */
setListening(CarrierTextCallback callback)291     public void setListening(CarrierTextCallback callback) {
292         mBgExecutor.execute(() -> handleSetListening(callback));
293     }
294 
getSubscriptionInfo()295     protected List<SubscriptionInfo> getSubscriptionInfo() {
296         return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
297     }
298 
updateCarrierText()299     protected void updateCarrierText() {
300         boolean allSimsMissing = true;
301         boolean anySimReadyAndInService = false;
302         CharSequence displayText = null;
303         List<SubscriptionInfo> subs = getSubscriptionInfo();
304 
305         final int numSubs = subs.size();
306         final int[] subsIds = new int[numSubs];
307         // This array will contain in position i, the index of subscription in slot ID i.
308         // -1 if no subscription in that slot
309         final int[] subOrderBySlot = new int[mSimSlotsNumber];
310         for (int i = 0; i < mSimSlotsNumber; i++) {
311             subOrderBySlot[i] = -1;
312         }
313         final CharSequence[] carrierNames = new CharSequence[numSubs];
314         if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
315 
316         for (int i = 0; i < numSubs; i++) {
317             int subId = subs.get(i).getSubscriptionId();
318             carrierNames[i] = "";
319             subsIds[i] = subId;
320             subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
321             int simState = mKeyguardUpdateMonitor.getSimState(subId);
322             CharSequence carrierName = subs.get(i).getCarrierName();
323             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
324             if (DEBUG) {
325                 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
326             }
327             if (carrierTextForSimState != null) {
328                 allSimsMissing = false;
329                 carrierNames[i] = carrierTextForSimState;
330             }
331             if (simState == TelephonyManager.SIM_STATE_READY) {
332                 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
333                 if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
334                     // hack for WFC (IWLAN) not turning off immediately once
335                     // Wi-Fi is disassociated or disabled
336                     if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
337                             || (mWifiManager != null && mWifiManager.isWifiEnabled()
338                             && mWifiManager.getConnectionInfo() != null
339                             && mWifiManager.getConnectionInfo().getBSSID() != null)) {
340                         if (DEBUG) {
341                             Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
342                         }
343                         anySimReadyAndInService = true;
344                     }
345                 }
346             }
347         }
348         // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
349         // This condition will also be true always when numSubs == 0
350         if (allSimsMissing && !anySimReadyAndInService) {
351             if (numSubs != 0) {
352                 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
353                 // This depends on mPlmn containing the text "Emergency calls only" when the radio
354                 // has some connectivity. Otherwise, it should be null or empty and just show
355                 // "No SIM card"
356                 // Grab the first subscripton, because they all should contain the emergency text,
357                 // described above.
358                 displayText = makeCarrierStringOnEmergencyCapable(
359                         getMissingSimMessage(), subs.get(0).getCarrierName());
360             } else {
361                 // We don't have a SubscriptionInfo to get the emergency calls only from.
362                 // Grab it from the old sticky broadcast if possible instead. We can use it
363                 // here because no subscriptions are active, so we don't have
364                 // to worry about MSIM clashing.
365                 CharSequence text =
366                         getContext().getText(com.android.internal.R.string.emergency_calls_only);
367                 Intent i = getContext().registerReceiver(null,
368                         new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
369                 if (i != null) {
370                     String spn = "";
371                     String plmn = "";
372                     if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
373                         spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
374                     }
375                     if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
376                         plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
377                     }
378                     if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
379                     if (Objects.equals(plmn, spn)) {
380                         text = plmn;
381                     } else {
382                         text = concatenate(plmn, spn, mSeparator);
383                     }
384                 }
385                 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
386             }
387         }
388 
389         if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
390 
391         displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
392                 allSimsMissing);
393 
394         boolean airplaneMode = false;
395         // APM (airplane mode) != no carrier state. There are carrier services
396         // (e.g. WFC = Wi-Fi calling) which may operate in APM.
397         if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
398             displayText = getAirplaneModeMessage();
399             airplaneMode = true;
400         }
401 
402         final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
403                 displayText,
404                 carrierNames,
405                 !allSimsMissing,
406                 subsIds,
407                 airplaneMode);
408         postToCallback(info);
409     }
410 
411     @VisibleForTesting
postToCallback(CarrierTextCallbackInfo info)412     protected void postToCallback(CarrierTextCallbackInfo info) {
413         final CarrierTextCallback callback = mCarrierTextCallback;
414         if (callback != null) {
415             mMainExecutor.execute(() -> callback.updateCarrierInfo(info));
416         }
417     }
418 
getContext()419     private Context getContext() {
420         return mContext;
421     }
422 
getMissingSimMessage()423     private String getMissingSimMessage() {
424         return mShowMissingSim && mTelephonyCapable
425                 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
426     }
427 
getAirplaneModeMessage()428     private String getAirplaneModeMessage() {
429         return mShowAirplaneMode
430                 ? getContext().getString(R.string.airplane_mode) : "";
431     }
432 
433     /**
434      * Top-level function for creating carrier text. Makes text based on simState, PLMN
435      * and SPN as well as device capabilities, such as being emergency call capable.
436      *
437      * @return Carrier text if not in missing state, null otherwise.
438      */
getCarrierTextForSimState(int simState, CharSequence text)439     private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
440         CharSequence carrierText = null;
441         CarrierTextManager.StatusMode status = getStatusForIccState(simState);
442         switch (status) {
443             case Normal:
444                 carrierText = text;
445                 break;
446 
447             case SimNotReady:
448                 // Null is reserved for denoting missing, in this case we have nothing to display.
449                 carrierText = ""; // nothing to display yet.
450                 break;
451 
452             case NetworkLocked:
453                 carrierText = makeCarrierStringOnEmergencyCapable(
454                         mContext.getText(R.string.keyguard_network_locked_message), text);
455                 break;
456 
457             case SimMissing:
458                 carrierText = null;
459                 break;
460 
461             case SimPermDisabled:
462                 carrierText = makeCarrierStringOnEmergencyCapable(
463                         getContext().getText(
464                                 R.string.keyguard_permanent_disabled_sim_message_short),
465                         text);
466                 break;
467 
468             case SimMissingLocked:
469                 carrierText = null;
470                 break;
471 
472             case SimLocked:
473                 carrierText = makeCarrierStringOnLocked(
474                         getContext().getText(R.string.keyguard_sim_locked_message),
475                         text);
476                 break;
477 
478             case SimPukLocked:
479                 carrierText = makeCarrierStringOnLocked(
480                         getContext().getText(R.string.keyguard_sim_puk_locked_message),
481                         text);
482                 break;
483             case SimIoError:
484                 carrierText = makeCarrierStringOnEmergencyCapable(
485                         getContext().getText(R.string.keyguard_sim_error_message_short),
486                         text);
487                 break;
488             case SimUnknown:
489                 carrierText = null;
490                 break;
491         }
492 
493         return carrierText;
494     }
495 
496     /*
497      * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
498      */
makeCarrierStringOnEmergencyCapable( CharSequence simMessage, CharSequence emergencyCallMessage)499     private CharSequence makeCarrierStringOnEmergencyCapable(
500             CharSequence simMessage, CharSequence emergencyCallMessage) {
501         if (mIsEmergencyCallCapable) {
502             return concatenate(simMessage, emergencyCallMessage, mSeparator);
503         }
504         return simMessage;
505     }
506 
507     /*
508      * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
509      * DSDS
510      */
makeCarrierStringOnLocked(CharSequence simMessage, CharSequence carrierName)511     private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
512             CharSequence carrierName) {
513         final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
514         final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
515         if (simMessageValid && carrierNameValid) {
516             return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
517                     carrierName, simMessage);
518         } else if (simMessageValid) {
519             return simMessage;
520         } else if (carrierNameValid) {
521             return carrierName;
522         } else {
523             return "";
524         }
525     }
526 
527     /**
528      * Determine the current status of the lock screen given the SIM state and other stuff.
529      */
getStatusForIccState(int simState)530     private CarrierTextManager.StatusMode getStatusForIccState(int simState) {
531         final boolean missingAndNotProvisioned =
532                 !mKeyguardUpdateMonitor.isDeviceProvisioned()
533                         && (simState == TelephonyManager.SIM_STATE_ABSENT
534                         || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
535 
536         // Assume we're NETWORK_LOCKED if not provisioned
537         simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
538         switch (simState) {
539             case TelephonyManager.SIM_STATE_ABSENT:
540                 return CarrierTextManager.StatusMode.SimMissing;
541             case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
542                 return CarrierTextManager.StatusMode.SimMissingLocked;
543             case TelephonyManager.SIM_STATE_NOT_READY:
544                 return CarrierTextManager.StatusMode.SimNotReady;
545             case TelephonyManager.SIM_STATE_PIN_REQUIRED:
546                 return CarrierTextManager.StatusMode.SimLocked;
547             case TelephonyManager.SIM_STATE_PUK_REQUIRED:
548                 return CarrierTextManager.StatusMode.SimPukLocked;
549             case TelephonyManager.SIM_STATE_READY:
550                 return CarrierTextManager.StatusMode.Normal;
551             case TelephonyManager.SIM_STATE_PERM_DISABLED:
552                 return CarrierTextManager.StatusMode.SimPermDisabled;
553             case TelephonyManager.SIM_STATE_UNKNOWN:
554                 return CarrierTextManager.StatusMode.SimUnknown;
555             case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
556                 return CarrierTextManager.StatusMode.SimIoError;
557         }
558         return CarrierTextManager.StatusMode.SimUnknown;
559     }
560 
concatenate(CharSequence plmn, CharSequence spn, CharSequence separator)561     private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
562             CharSequence separator) {
563         final boolean plmnValid = !TextUtils.isEmpty(plmn);
564         final boolean spnValid = !TextUtils.isEmpty(spn);
565         if (plmnValid && spnValid) {
566             return new StringBuilder().append(plmn).append(separator).append(spn).toString();
567         } else if (plmnValid) {
568             return plmn;
569         } else if (spnValid) {
570             return spn;
571         } else {
572             return "";
573         }
574     }
575 
576     /**
577      * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
578      * separator added so there are no extra separators that are not needed.
579      */
joinNotEmpty(CharSequence separator, CharSequence[] sequences)580     private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
581         int length = sequences.length;
582         if (length == 0) return "";
583         StringBuilder sb = new StringBuilder();
584         for (int i = 0; i < length; i++) {
585             if (!TextUtils.isEmpty(sequences[i])) {
586                 if (!TextUtils.isEmpty(sb)) {
587                     sb.append(separator);
588                 }
589                 sb.append(sequences[i]);
590             }
591         }
592         return sb.toString();
593     }
594 
append(List<CharSequence> list, CharSequence string)595     private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
596         if (!TextUtils.isEmpty(string)) {
597             list.add(string);
598         }
599         return list;
600     }
601 
getCarrierHelpTextForSimState(int simState, String plmn, String spn)602     private CharSequence getCarrierHelpTextForSimState(int simState,
603             String plmn, String spn) {
604         int carrierHelpTextId = 0;
605         CarrierTextManager.StatusMode status = getStatusForIccState(simState);
606         switch (status) {
607             case NetworkLocked:
608                 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
609                 break;
610 
611             case SimMissing:
612                 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
613                 break;
614 
615             case SimPermDisabled:
616                 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
617                 break;
618 
619             case SimMissingLocked:
620                 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
621                 break;
622 
623             case Normal:
624             case SimLocked:
625             case SimPukLocked:
626                 break;
627         }
628 
629         return mContext.getText(carrierHelpTextId);
630     }
631 
632     /** Injectable Buildeer for {@#link CarrierTextManager}. */
633     public static class Builder {
634         private final Context mContext;
635         private final String mSeparator;
636         private final WifiManager mWifiManager;
637         private final TelephonyManager mTelephonyManager;
638         private final TelephonyListenerManager mTelephonyListenerManager;
639         private final WakefulnessLifecycle mWakefulnessLifecycle;
640         private final Executor mMainExecutor;
641         private final Executor mBgExecutor;
642         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
643         private boolean mShowAirplaneMode;
644         private boolean mShowMissingSim;
645 
646         @Inject
Builder( Context context, @Main Resources resources, @Nullable WifiManager wifiManager, TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, WakefulnessLifecycle wakefulnessLifecycle, @Main Executor mainExecutor, @Background Executor bgExecutor, KeyguardUpdateMonitor keyguardUpdateMonitor)647         public Builder(
648                 Context context,
649                 @Main Resources resources,
650                 @Nullable WifiManager wifiManager,
651                 TelephonyManager telephonyManager,
652                 TelephonyListenerManager telephonyListenerManager,
653                 WakefulnessLifecycle wakefulnessLifecycle,
654                 @Main Executor mainExecutor,
655                 @Background Executor bgExecutor,
656                 KeyguardUpdateMonitor keyguardUpdateMonitor) {
657             mContext = context;
658             mSeparator = resources.getString(
659                     com.android.internal.R.string.kg_text_message_separator);
660             mWifiManager = wifiManager;
661             mTelephonyManager = telephonyManager;
662             mTelephonyListenerManager = telephonyListenerManager;
663             mWakefulnessLifecycle = wakefulnessLifecycle;
664             mMainExecutor = mainExecutor;
665             mBgExecutor = bgExecutor;
666             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
667         }
668 
669         /** */
setShowAirplaneMode(boolean showAirplaneMode)670         public Builder setShowAirplaneMode(boolean showAirplaneMode) {
671             mShowAirplaneMode = showAirplaneMode;
672             return this;
673         }
674 
675         /** */
setShowMissingSim(boolean showMissingSim)676         public Builder setShowMissingSim(boolean showMissingSim) {
677             mShowMissingSim = showMissingSim;
678             return this;
679         }
680 
681         /** Create a CarrierTextManager. */
build()682         public CarrierTextManager build() {
683             return new CarrierTextManager(
684                     mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
685                     mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
686                     mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
687         }
688     }
689     /**
690      * Data structure for passing information to CarrierTextController subscribers
691      */
692     public static final class CarrierTextCallbackInfo {
693         public final CharSequence carrierText;
694         public final CharSequence[] listOfCarriers;
695         public final boolean anySimReady;
696         public final int[] subscriptionIds;
697         public boolean airplaneMode;
698 
699         @VisibleForTesting
CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, boolean anySimReady, int[] subscriptionIds)700         public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
701                 boolean anySimReady, int[] subscriptionIds) {
702             this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
703         }
704 
705         @VisibleForTesting
CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, boolean anySimReady, int[] subscriptionIds, boolean airplaneMode)706         public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
707                 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
708             this.carrierText = carrierText;
709             this.listOfCarriers = listOfCarriers;
710             this.anySimReady = anySimReady;
711             this.subscriptionIds = subscriptionIds;
712             this.airplaneMode = airplaneMode;
713         }
714     }
715 
716     /**
717      * Callback to communicate to Views
718      */
719     public interface CarrierTextCallback {
720         /**
721          * Provides updated carrier information.
722          */
updateCarrierInfo(CarrierTextCallbackInfo info)723         default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
724 
725         /**
726          * Notifies the View that the device is going to sleep
727          */
startedGoingToSleep()728         default void startedGoingToSleep() {};
729 
730         /**
731          * Notifies the View that the device finished waking up
732          */
finishedWakingUp()733         default void finishedWakingUp() {};
734     }
735 }
736