blob: 722c9bfd075775ac5ae83d9f54185204ffbd217a [file] [log] [blame]
Fabian Kozynski02941af2019-01-17 17:57:37 -05001/*
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
17package com.android.keyguard;
18
Malcolm Chen06ec3572019-04-09 15:55:29 -070019import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
20import static android.telephony.PhoneStateListener.LISTEN_NONE;
21
22import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
23
Fabian Kozynski02941af2019-01-17 17:57:37 -050024import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.net.ConnectivityManager;
28import android.net.wifi.WifiManager;
Fabian Kozynskibf6fef32019-02-04 09:21:38 -050029import android.os.Handler;
Malcolm Chen06ec3572019-04-09 15:55:29 -070030import android.telephony.CarrierConfigManager;
31import android.telephony.PhoneStateListener;
Fabian Kozynski02941af2019-01-17 17:57:37 -050032import android.telephony.ServiceState;
33import android.telephony.SubscriptionInfo;
Malcolm Chen06ec3572019-04-09 15:55:29 -070034import android.telephony.SubscriptionManager;
Fabian Kozynski02941af2019-01-17 17:57:37 -050035import android.telephony.TelephonyManager;
36import android.text.TextUtils;
37import android.util.Log;
38
Fabian Kozynskibf6fef32019-02-04 09:21:38 -050039import androidx.annotation.VisibleForTesting;
40
Fabian Kozynski02941af2019-01-17 17:57:37 -050041import com.android.internal.telephony.IccCardConstants;
42import com.android.internal.telephony.TelephonyIntents;
43import com.android.settingslib.WirelessUtils;
44import com.android.systemui.Dependency;
45import com.android.systemui.keyguard.WakefulnessLifecycle;
46
Malcolm Chen06ec3572019-04-09 15:55:29 -070047import java.util.ArrayList;
Fabian Kozynski02941af2019-01-17 17:57:37 -050048import java.util.List;
49import java.util.Objects;
50
51/**
52 * Controller that generates text including the carrier names and/or the status of all the SIM
53 * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
54 * separated by a given separator {@link CharSequence}.
55 */
56public class CarrierTextController {
57 private static final boolean DEBUG = KeyguardConstants.DEBUG;
58 private static final String TAG = "CarrierTextController";
59
60 private final boolean mIsEmergencyCallCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050061 private boolean mTelephonyCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050062 private boolean mShowMissingSim;
Fabian Kozynski02941af2019-01-17 17:57:37 -050063 private boolean mShowAirplaneMode;
Fabian Kozynskib176f422019-02-05 09:36:59 -050064 @VisibleForTesting
65 protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
Fabian Kozynski02941af2019-01-17 17:57:37 -050066 private WifiManager mWifiManager;
Fabian Kozynskib176f422019-02-05 09:36:59 -050067 private boolean[] mSimErrorState;
68 private final int mSimSlotsNumber;
Fabian Kozynski02941af2019-01-17 17:57:37 -050069 private CarrierTextCallback mCarrierTextCallback;
70 private Context mContext;
71 private CharSequence mSeparator;
72 private WakefulnessLifecycle mWakefulnessLifecycle;
73 private final WakefulnessLifecycle.Observer mWakefulnessObserver =
74 new WakefulnessLifecycle.Observer() {
75 @Override
76 public void onFinishedWakingUp() {
77 mCarrierTextCallback.finishedWakingUp();
78 }
79
80 @Override
81 public void onStartedGoingToSleep() {
82 mCarrierTextCallback.startedGoingToSleep();
83 }
84 };
85
Fabian Kozynskib176f422019-02-05 09:36:59 -050086 @VisibleForTesting
87 protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
Fabian Kozynski02941af2019-01-17 17:57:37 -050088 @Override
89 public void onRefreshCarrierInfo() {
90 if (DEBUG) {
91 Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
92 + Boolean.toString(mTelephonyCapable));
93 }
94 updateCarrierText();
95 }
96
97 @Override
98 public void onTelephonyCapable(boolean capable) {
99 if (DEBUG) {
100 Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
101 + Boolean.toString(capable));
102 }
103 mTelephonyCapable = capable;
104 updateCarrierText();
105 }
106
107 public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500108 if (slotId < 0 || slotId >= mSimSlotsNumber) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500109 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
110 + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
111 return;
112 }
113
114 if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
115 if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
116 mSimErrorState[slotId] = true;
117 updateCarrierText();
118 } else if (mSimErrorState[slotId]) {
119 mSimErrorState[slotId] = false;
120 updateCarrierText();
121 }
122 }
123 };
124
Malcolm Chen06ec3572019-04-09 15:55:29 -0700125 private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
126 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
127 @Override
128 public void onActiveDataSubscriptionIdChanged(int subId) {
129 mActiveMobileDataSubscription = subId;
130 if (mKeyguardUpdateMonitor != null) {
131 updateCarrierText();
132 }
133 }
134 };
135
Fabian Kozynski02941af2019-01-17 17:57:37 -0500136 /**
137 * The status of this lock screen. Primarily used for widgets on LockScreen.
138 */
139 private enum StatusMode {
140 Normal, // Normal case (sim card present, it's not locked)
141 NetworkLocked, // SIM card is 'network locked'.
142 SimMissing, // SIM card is missing.
143 SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
144 SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
145 SimLocked, // SIM card is currently locked
146 SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
147 SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
148 SimIoError, // SIM card is faulty
149 SimUnknown // SIM card is unknown
150 }
151
152 /**
153 * Controller that provides updates on text with carriers names or SIM status.
154 * Used by {@link CarrierText}.
Fabian Kozynski1823f112019-01-18 11:43:29 -0500155 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500156 * @param separator Separator between different parts of the text
Fabian Kozynski02941af2019-01-17 17:57:37 -0500157 */
158 public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
159 boolean showMissingSim) {
160 mContext = context;
161 mIsEmergencyCallCapable = context.getResources().getBoolean(
162 com.android.internal.R.bool.config_voice_capable);
163
164 mShowAirplaneMode = showAirplaneMode;
165 mShowMissingSim = showMissingSim;
166
167 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
168 mSeparator = separator;
169 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500170 mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
171 Context.TELEPHONY_SERVICE)).getPhoneCount();
172 mSimErrorState = new boolean[mSimSlotsNumber];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500173 }
174
175 /**
176 * Checks if there are faulty cards. Adds the text depending on the slot of the card
177 *
178 * @param text: current carrier text based on the sim state
Fabian Kozynskib176f422019-02-05 09:36:59 -0500179 * @param carrierNames names order by subscription order
180 * @param subOrderBySlot array containing the sub index for each slot ID
Fabian Kozynski02941af2019-01-17 17:57:37 -0500181 * @param noSims: whether a valid sim card is inserted
182 * @return text
183 */
Fabian Kozynskib176f422019-02-05 09:36:59 -0500184 private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
185 CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500186 final CharSequence carrier = "";
187 CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
188 IccCardConstants.State.CARD_IO_ERROR, carrier);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500189 // mSimErrorState has the state of each sim indexed by slotID.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500190 for (int index = 0; index < mSimErrorState.length; index++) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500191 if (!mSimErrorState[index]) {
192 continue;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500193 }
Fabian Kozynskib176f422019-02-05 09:36:59 -0500194 // In the case when no sim cards are detected but a faulty card is inserted
195 // overwrite the text and only show "Invalid card"
196 if (noSims) {
197 return concatenate(carrierTextForSimIOError,
198 getContext().getText(
199 com.android.internal.R.string.emergency_calls_only),
200 mSeparator);
201 } else if (subOrderBySlot[index] != -1) {
202 int subIndex = subOrderBySlot[index];
203 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
204 carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
205 carrierNames[subIndex],
206 mSeparator);
207 } else {
208 // concatenate "Invalid card" when faulty card is inserted in other slot
209 text = concatenate(text, carrierTextForSimIOError, mSeparator);
210 }
211
Fabian Kozynski02941af2019-01-17 17:57:37 -0500212 }
213 return text;
214 }
215
216 /**
217 * Sets the listening status of this controller. If the callback is null, it is set to
218 * not listening
Fabian Kozynski1823f112019-01-18 11:43:29 -0500219 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500220 * @param callback Callback to provide text updates
221 */
222 public void setListening(CarrierTextCallback callback) {
Malcolm Chen06ec3572019-04-09 15:55:29 -0700223 TelephonyManager telephonyManager = ((TelephonyManager) mContext
224 .getSystemService(Context.TELEPHONY_SERVICE));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500225 if (callback != null) {
226 mCarrierTextCallback = callback;
227 if (ConnectivityManager.from(mContext).isNetworkSupported(
228 ConnectivityManager.TYPE_MOBILE)) {
229 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
230 mKeyguardUpdateMonitor.registerCallback(mCallback);
231 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
Malcolm Chen06ec3572019-04-09 15:55:29 -0700232 telephonyManager.listen(mPhoneStateListener,
233 LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500234 } else {
235 // Don't listen and clear out the text when the device isn't a phone.
236 mKeyguardUpdateMonitor = null;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500237 callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500238 }
239 } else {
240 mCarrierTextCallback = null;
241 if (mKeyguardUpdateMonitor != null) {
242 mKeyguardUpdateMonitor.removeCallback(mCallback);
243 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
244 }
Malcolm Chen06ec3572019-04-09 15:55:29 -0700245 telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
246 }
247 }
248
249 /**
250 * STOPSHIP(b/130246708) remove when no longer needed for testing purpose.
251 * @param subscriptions
252 */
253 private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
254 if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
255 SubscriptionInfo info1 = subscriptions.get(0);
256 SubscriptionInfo info2 = subscriptions.get(1);
257 if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
258 // If both subscriptions are primary, show both.
259 if (!info1.isOpportunistic() && !info2.isOpportunistic()) return;
260
261 // If carrier required, always show signal bar of primary subscription.
262 // Otherwise, show whichever subscription is currently active for Internet.
263 boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig()
264 .getBoolean(CarrierConfigManager
265 .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN);
266 if (alwaysShowPrimary) {
267 subscriptions.remove(info1.isOpportunistic() ? info1 : info2);
268 } else {
269 subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription
270 ? info2 : info1);
271 }
272
273 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500274 }
275 }
276
277 protected void updateCarrierText() {
278 boolean allSimsMissing = true;
279 boolean anySimReadyAndInService = false;
280 CharSequence displayText = null;
281
Malcolm Chen06ec3572019-04-09 15:55:29 -0700282 // STOPSHIP(b/130246708) revert to mKeyguardUpdateMonitor.getSubscriptionInfo(false).
283 SubscriptionManager subscriptionManager = ((SubscriptionManager) mContext.getSystemService(
284 Context.TELEPHONY_SUBSCRIPTION_SERVICE));
285 List<SubscriptionInfo> subs = subscriptionManager.getActiveSubscriptionInfoList(false);
286
287 if (subs == null) {
288 subs = new ArrayList<>();
289 } else {
290 filterMobileSubscriptionInSameGroup(subs);
291 }
292
Fabian Kozynski02941af2019-01-17 17:57:37 -0500293 final int numSubs = subs.size();
Fabian Kozynski1823f112019-01-18 11:43:29 -0500294 final int[] subsIds = new int[numSubs];
Fabian Kozynskib176f422019-02-05 09:36:59 -0500295 // 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];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500302 if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500303
Fabian Kozynski02941af2019-01-17 17:57:37 -0500304 for (int i = 0; i < numSubs; i++) {
305 int subId = subs.get(i).getSubscriptionId();
Fabian Kozynskib176f422019-02-05 09:36:59 -0500306 carrierNames[i] = "";
Fabian Kozynski1823f112019-01-18 11:43:29 -0500307 subsIds[i] = subId;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500308 subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500309 IccCardConstants.State 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;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500317 carrierNames[i] = carrierTextForSimState;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500318 }
319 if (simState == IccCardConstants.State.READY) {
320 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
321 if (ss != null && ss.getDataRegState() == 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 if (allSimsMissing) {
337 if (numSubs != 0) {
338 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
339 // This depends on mPlmn containing the text "Emergency calls only" when the radio
340 // has some connectivity. Otherwise, it should be null or empty and just show
341 // "No SIM card"
342 // Grab the first subscripton, because they all should contain the emergency text,
343 // described above.
344 displayText = makeCarrierStringOnEmergencyCapable(
345 getMissingSimMessage(), subs.get(0).getCarrierName());
346 } else {
347 // We don't have a SubscriptionInfo to get the emergency calls only from.
348 // Grab it from the old sticky broadcast if possible instead. We can use it
349 // here because no subscriptions are active, so we don't have
350 // to worry about MSIM clashing.
351 CharSequence text =
352 getContext().getText(com.android.internal.R.string.emergency_calls_only);
353 Intent i = getContext().registerReceiver(null,
354 new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
355 if (i != null) {
356 String spn = "";
357 String plmn = "";
358 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
359 spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
360 }
361 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
362 plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
363 }
364 if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
365 if (Objects.equals(plmn, spn)) {
366 text = plmn;
367 } else {
368 text = concatenate(plmn, spn, mSeparator);
369 }
370 }
371 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
372 }
373 }
374
Fabian Kozynskib176f422019-02-05 09:36:59 -0500375 displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
376 allSimsMissing);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500377 // APM (airplane mode) != no carrier state. There are carrier services
378 // (e.g. WFC = Wi-Fi calling) which may operate in APM.
379 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
380 displayText = getAirplaneModeMessage();
381 }
382
Fabian Kozynskib176f422019-02-05 09:36:59 -0500383 if (TextUtils.isEmpty(displayText)) {
384 displayText = TextUtils.join(mSeparator, carrierNames);
385 }
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500386 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
387 displayText,
Fabian Kozynskib176f422019-02-05 09:36:59 -0500388 carrierNames,
389 !allSimsMissing,
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500390 subsIds);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500391 postToCallback(info);
392 }
393
394 @VisibleForTesting
395 protected void postToCallback(CarrierTextCallbackInfo info) {
396 Handler handler = Dependency.get(Dependency.MAIN_HANDLER);
Fabian Kozynski48343c32019-02-07 10:28:50 -0500397 final CarrierTextCallback callback = mCarrierTextCallback;
398 if (callback != null) {
399 handler.post(() -> callback.updateCarrierInfo(info));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500400 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500401 }
402
403 private Context getContext() {
404 return mContext;
405 }
406
407 private String getMissingSimMessage() {
408 return mShowMissingSim && mTelephonyCapable
409 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
410 }
411
412 private String getAirplaneModeMessage() {
413 return mShowAirplaneMode
414 ? getContext().getString(R.string.airplane_mode) : "";
415 }
416
417 /**
418 * Top-level function for creating carrier text. Makes text based on simState, PLMN
419 * and SPN as well as device capabilities, such as being emergency call capable.
420 *
421 * @return Carrier text if not in missing state, null otherwise.
422 */
423 private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
424 CharSequence text) {
425 CharSequence carrierText = null;
426 CarrierTextController.StatusMode status = getStatusForIccState(simState);
427 switch (status) {
428 case Normal:
429 carrierText = text;
430 break;
431
432 case SimNotReady:
433 // Null is reserved for denoting missing, in this case we have nothing to display.
434 carrierText = ""; // nothing to display yet.
435 break;
436
437 case NetworkLocked:
438 carrierText = makeCarrierStringOnEmergencyCapable(
439 mContext.getText(R.string.keyguard_network_locked_message), text);
440 break;
441
442 case SimMissing:
443 carrierText = null;
444 break;
445
446 case SimPermDisabled:
447 carrierText = makeCarrierStringOnEmergencyCapable(
448 getContext().getText(
449 R.string.keyguard_permanent_disabled_sim_message_short),
450 text);
451 break;
452
453 case SimMissingLocked:
454 carrierText = null;
455 break;
456
457 case SimLocked:
458 carrierText = makeCarrierStringOnEmergencyCapable(
459 getContext().getText(R.string.keyguard_sim_locked_message),
460 text);
461 break;
462
463 case SimPukLocked:
464 carrierText = makeCarrierStringOnEmergencyCapable(
465 getContext().getText(R.string.keyguard_sim_puk_locked_message),
466 text);
467 break;
468 case SimIoError:
469 carrierText = makeCarrierStringOnEmergencyCapable(
470 getContext().getText(R.string.keyguard_sim_error_message_short),
471 text);
472 break;
473 case SimUnknown:
474 carrierText = null;
475 break;
476 }
477
478 return carrierText;
479 }
480
481 /*
482 * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
483 */
484 private CharSequence makeCarrierStringOnEmergencyCapable(
485 CharSequence simMessage, CharSequence emergencyCallMessage) {
486 if (mIsEmergencyCallCapable) {
487 return concatenate(simMessage, emergencyCallMessage, mSeparator);
488 }
489 return simMessage;
490 }
491
492 /**
493 * Determine the current status of the lock screen given the SIM state and other stuff.
494 */
495 private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
496 // Since reading the SIM may take a while, we assume it is present until told otherwise.
497 if (simState == null) {
498 return CarrierTextController.StatusMode.Normal;
499 }
500
501 final boolean missingAndNotProvisioned =
502 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
503 && (simState == IccCardConstants.State.ABSENT
504 || simState == IccCardConstants.State.PERM_DISABLED);
505
506 // Assume we're NETWORK_LOCKED if not provisioned
507 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
508 switch (simState) {
509 case ABSENT:
510 return CarrierTextController.StatusMode.SimMissing;
511 case NETWORK_LOCKED:
512 return CarrierTextController.StatusMode.SimMissingLocked;
513 case NOT_READY:
514 return CarrierTextController.StatusMode.SimNotReady;
515 case PIN_REQUIRED:
516 return CarrierTextController.StatusMode.SimLocked;
517 case PUK_REQUIRED:
518 return CarrierTextController.StatusMode.SimPukLocked;
519 case READY:
520 return CarrierTextController.StatusMode.Normal;
521 case PERM_DISABLED:
522 return CarrierTextController.StatusMode.SimPermDisabled;
523 case UNKNOWN:
524 return CarrierTextController.StatusMode.SimUnknown;
525 case CARD_IO_ERROR:
526 return CarrierTextController.StatusMode.SimIoError;
527 }
528 return CarrierTextController.StatusMode.SimUnknown;
529 }
530
531 private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
532 CharSequence separator) {
533 final boolean plmnValid = !TextUtils.isEmpty(plmn);
534 final boolean spnValid = !TextUtils.isEmpty(spn);
535 if (plmnValid && spnValid) {
536 return new StringBuilder().append(plmn).append(separator).append(spn).toString();
537 } else if (plmnValid) {
538 return plmn;
539 } else if (spnValid) {
540 return spn;
541 } else {
542 return "";
543 }
544 }
545
546 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
547 if (!TextUtils.isEmpty(string)) {
548 list.add(string);
549 }
550 return list;
551 }
552
553 private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
554 String plmn, String spn) {
555 int carrierHelpTextId = 0;
556 CarrierTextController.StatusMode status = getStatusForIccState(simState);
557 switch (status) {
558 case NetworkLocked:
559 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
560 break;
561
562 case SimMissing:
563 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
564 break;
565
566 case SimPermDisabled:
567 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
568 break;
569
570 case SimMissingLocked:
571 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
572 break;
573
574 case Normal:
575 case SimLocked:
576 case SimPukLocked:
577 break;
578 }
579
580 return mContext.getText(carrierHelpTextId);
581 }
582
583 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500584 * Data structure for passing information to CarrierTextController subscribers
585 */
586 public static final class CarrierTextCallbackInfo {
587 public final CharSequence carrierText;
588 public final CharSequence[] listOfCarriers;
589 public final boolean anySimReady;
590 public final int[] subscriptionIds;
591
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500592 @VisibleForTesting
593 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
Fabian Kozynski1823f112019-01-18 11:43:29 -0500594 boolean anySimReady, int[] subscriptionIds) {
595 this.carrierText = carrierText;
596 this.listOfCarriers = listOfCarriers;
597 this.anySimReady = anySimReady;
598 this.subscriptionIds = subscriptionIds;
599 }
600 }
601
602 /**
Fabian Kozynski02941af2019-01-17 17:57:37 -0500603 * Callback to communicate to Views
604 */
605 public interface CarrierTextCallback {
606 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500607 * Provides updated carrier information.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500608 */
Fabian Kozynski1823f112019-01-18 11:43:29 -0500609 default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
Fabian Kozynski02941af2019-01-17 17:57:37 -0500610
611 /**
612 * Notifies the View that the device is going to sleep
613 */
614 default void startedGoingToSleep() {};
615
616 /**
617 * Notifies the View that the device finished waking up
618 */
619 default void finishedWakingUp() {};
620 }
621}