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