blob: 2e9b03c6b31e961da08063e0b97c2092b45702e9 [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 Kozynskib38edbb2019-04-12 12:20:13 -0400377 boolean airplaneMode = false;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500378 // APM (airplane mode) != no carrier state. There are carrier services
379 // (e.g. WFC = Wi-Fi calling) which may operate in APM.
380 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
381 displayText = getAirplaneModeMessage();
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400382 airplaneMode = true;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500383 }
384
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400385 if (TextUtils.isEmpty(displayText) && !airplaneMode) {
Fabian Kozynski00d02f12019-04-15 09:48:30 -0400386 displayText = joinNotEmpty(mSeparator, carrierNames);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500387 }
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500388 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
389 displayText,
Fabian Kozynskib176f422019-02-05 09:36:59 -0500390 carrierNames,
391 !allSimsMissing,
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400392 subsIds,
393 airplaneMode);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500394 postToCallback(info);
395 }
396
397 @VisibleForTesting
398 protected void postToCallback(CarrierTextCallbackInfo info) {
399 Handler handler = Dependency.get(Dependency.MAIN_HANDLER);
Fabian Kozynski48343c32019-02-07 10:28:50 -0500400 final CarrierTextCallback callback = mCarrierTextCallback;
401 if (callback != null) {
402 handler.post(() -> callback.updateCarrierInfo(info));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500403 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500404 }
405
406 private Context getContext() {
407 return mContext;
408 }
409
410 private String getMissingSimMessage() {
411 return mShowMissingSim && mTelephonyCapable
412 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
413 }
414
415 private String getAirplaneModeMessage() {
416 return mShowAirplaneMode
417 ? getContext().getString(R.string.airplane_mode) : "";
418 }
419
420 /**
421 * Top-level function for creating carrier text. Makes text based on simState, PLMN
422 * and SPN as well as device capabilities, such as being emergency call capable.
423 *
424 * @return Carrier text if not in missing state, null otherwise.
425 */
426 private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
427 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 = makeCarrierStringOnEmergencyCapable(
462 getContext().getText(R.string.keyguard_sim_locked_message),
463 text);
464 break;
465
466 case SimPukLocked:
467 carrierText = makeCarrierStringOnEmergencyCapable(
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 */
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 * Determine the current status of the lock screen given the SIM state and other stuff.
497 */
498 private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
499 // Since reading the SIM may take a while, we assume it is present until told otherwise.
500 if (simState == null) {
501 return CarrierTextController.StatusMode.Normal;
502 }
503
504 final boolean missingAndNotProvisioned =
505 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
506 && (simState == IccCardConstants.State.ABSENT
507 || simState == IccCardConstants.State.PERM_DISABLED);
508
509 // Assume we're NETWORK_LOCKED if not provisioned
510 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
511 switch (simState) {
512 case ABSENT:
513 return CarrierTextController.StatusMode.SimMissing;
514 case NETWORK_LOCKED:
515 return CarrierTextController.StatusMode.SimMissingLocked;
516 case NOT_READY:
517 return CarrierTextController.StatusMode.SimNotReady;
518 case PIN_REQUIRED:
519 return CarrierTextController.StatusMode.SimLocked;
520 case PUK_REQUIRED:
521 return CarrierTextController.StatusMode.SimPukLocked;
522 case READY:
523 return CarrierTextController.StatusMode.Normal;
524 case PERM_DISABLED:
525 return CarrierTextController.StatusMode.SimPermDisabled;
526 case UNKNOWN:
527 return CarrierTextController.StatusMode.SimUnknown;
528 case CARD_IO_ERROR:
529 return CarrierTextController.StatusMode.SimIoError;
530 }
531 return CarrierTextController.StatusMode.SimUnknown;
532 }
533
534 private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
535 CharSequence separator) {
536 final boolean plmnValid = !TextUtils.isEmpty(plmn);
537 final boolean spnValid = !TextUtils.isEmpty(spn);
538 if (plmnValid && spnValid) {
539 return new StringBuilder().append(plmn).append(separator).append(spn).toString();
540 } else if (plmnValid) {
541 return plmn;
542 } else if (spnValid) {
543 return spn;
544 } else {
545 return "";
546 }
547 }
548
Fabian Kozynski00d02f12019-04-15 09:48:30 -0400549 /**
550 * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
551 * separator added so there are no extra separators that are not needed.
552 */
553 private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
554 int length = sequences.length;
555 if (length == 0) return "";
556 StringBuilder sb = new StringBuilder();
557 for (int i = 0; i < length; i++) {
558 if (!TextUtils.isEmpty(sequences[i])) {
559 if (!TextUtils.isEmpty(sb)) {
560 sb.append(separator);
561 }
562 sb.append(sequences[i]);
563 }
564 }
565 return sb.toString();
566 }
567
Fabian Kozynski02941af2019-01-17 17:57:37 -0500568 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
569 if (!TextUtils.isEmpty(string)) {
570 list.add(string);
571 }
572 return list;
573 }
574
575 private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
576 String plmn, String spn) {
577 int carrierHelpTextId = 0;
578 CarrierTextController.StatusMode status = getStatusForIccState(simState);
579 switch (status) {
580 case NetworkLocked:
581 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
582 break;
583
584 case SimMissing:
585 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
586 break;
587
588 case SimPermDisabled:
589 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
590 break;
591
592 case SimMissingLocked:
593 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
594 break;
595
596 case Normal:
597 case SimLocked:
598 case SimPukLocked:
599 break;
600 }
601
602 return mContext.getText(carrierHelpTextId);
603 }
604
605 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500606 * Data structure for passing information to CarrierTextController subscribers
607 */
608 public static final class CarrierTextCallbackInfo {
609 public final CharSequence carrierText;
610 public final CharSequence[] listOfCarriers;
611 public final boolean anySimReady;
612 public final int[] subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400613 public boolean airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500614
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500615 @VisibleForTesting
616 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
Fabian Kozynski1823f112019-01-18 11:43:29 -0500617 boolean anySimReady, int[] subscriptionIds) {
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400618 this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
619 }
620
621 @VisibleForTesting
622 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
623 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
Fabian Kozynski1823f112019-01-18 11:43:29 -0500624 this.carrierText = carrierText;
625 this.listOfCarriers = listOfCarriers;
626 this.anySimReady = anySimReady;
627 this.subscriptionIds = subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400628 this.airplaneMode = airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500629 }
630 }
631
632 /**
Fabian Kozynski02941af2019-01-17 17:57:37 -0500633 * Callback to communicate to Views
634 */
635 public interface CarrierTextCallback {
636 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500637 * Provides updated carrier information.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500638 */
Fabian Kozynski1823f112019-01-18 11:43:29 -0500639 default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
Fabian Kozynski02941af2019-01-17 17:57:37 -0500640
641 /**
642 * Notifies the View that the device is going to sleep
643 */
644 default void startedGoingToSleep() {};
645
646 /**
647 * Notifies the View that the device finished waking up
648 */
649 default void finishedWakingUp() {};
650 }
651}