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