blob: 107d5cce3f8e99677ed1d53e12883480cfc88e24 [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
Lucas Dupin8968f6a2019-08-09 17:41:15 -070022import static com.android.systemui.DejankUtils.whitelistIpcs;
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.PhoneStateListener;
Fabian Kozynski02941af2019-01-17 17:57:37 -050031import android.telephony.ServiceState;
32import android.telephony.SubscriptionInfo;
Malcolm Chen06ec3572019-04-09 15:55:29 -070033import android.telephony.SubscriptionManager;
Fabian Kozynski02941af2019-01-17 17:57:37 -050034import android.telephony.TelephonyManager;
35import android.text.TextUtils;
36import android.util.Log;
37
Lucas Dupin8968f6a2019-08-09 17:41:15 -070038import androidx.annotation.VisibleForTesting;
39
Fabian Kozynski02941af2019-01-17 17:57:37 -050040import com.android.internal.telephony.IccCardConstants;
41import com.android.internal.telephony.TelephonyIntents;
42import com.android.settingslib.WirelessUtils;
43import com.android.systemui.Dependency;
44import com.android.systemui.keyguard.WakefulnessLifecycle;
45
46import java.util.List;
47import java.util.Objects;
48
49/**
50 * Controller that generates text including the carrier names and/or the status of all the SIM
51 * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
52 * separated by a given separator {@link CharSequence}.
53 */
54public class CarrierTextController {
55 private static final boolean DEBUG = KeyguardConstants.DEBUG;
56 private static final String TAG = "CarrierTextController";
57
58 private final boolean mIsEmergencyCallCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050059 private boolean mTelephonyCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050060 private boolean mShowMissingSim;
Fabian Kozynski02941af2019-01-17 17:57:37 -050061 private boolean mShowAirplaneMode;
Fabian Kozynskib176f422019-02-05 09:36:59 -050062 @VisibleForTesting
63 protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
Fabian Kozynski02941af2019-01-17 17:57:37 -050064 private WifiManager mWifiManager;
Fabian Kozynskib176f422019-02-05 09:36:59 -050065 private boolean[] mSimErrorState;
66 private final int mSimSlotsNumber;
Fabian Kozynski02941af2019-01-17 17:57:37 -050067 private CarrierTextCallback mCarrierTextCallback;
68 private Context mContext;
69 private CharSequence mSeparator;
70 private WakefulnessLifecycle mWakefulnessLifecycle;
71 private final WakefulnessLifecycle.Observer mWakefulnessObserver =
72 new WakefulnessLifecycle.Observer() {
73 @Override
74 public void onFinishedWakingUp() {
75 mCarrierTextCallback.finishedWakingUp();
76 }
77
78 @Override
79 public void onStartedGoingToSleep() {
80 mCarrierTextCallback.startedGoingToSleep();
81 }
82 };
83
Fabian Kozynskib176f422019-02-05 09:36:59 -050084 @VisibleForTesting
85 protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
Fabian Kozynski02941af2019-01-17 17:57:37 -050086 @Override
87 public void onRefreshCarrierInfo() {
88 if (DEBUG) {
89 Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
90 + Boolean.toString(mTelephonyCapable));
91 }
92 updateCarrierText();
93 }
94
95 @Override
96 public void onTelephonyCapable(boolean capable) {
97 if (DEBUG) {
98 Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
99 + Boolean.toString(capable));
100 }
101 mTelephonyCapable = capable;
102 updateCarrierText();
103 }
104
105 public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500106 if (slotId < 0 || slotId >= mSimSlotsNumber) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500107 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
108 + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
109 return;
110 }
111
112 if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
113 if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
114 mSimErrorState[slotId] = true;
115 updateCarrierText();
116 } else if (mSimErrorState[slotId]) {
117 mSimErrorState[slotId] = false;
118 updateCarrierText();
119 }
120 }
121 };
122
Malcolm Chen06ec3572019-04-09 15:55:29 -0700123 private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
124 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
125 @Override
126 public void onActiveDataSubscriptionIdChanged(int subId) {
127 mActiveMobileDataSubscription = subId;
128 if (mKeyguardUpdateMonitor != null) {
129 updateCarrierText();
130 }
131 }
132 };
133
Fabian Kozynski02941af2019-01-17 17:57:37 -0500134 /**
135 * The status of this lock screen. Primarily used for widgets on LockScreen.
136 */
137 private enum StatusMode {
138 Normal, // Normal case (sim card present, it's not locked)
139 NetworkLocked, // SIM card is 'network locked'.
140 SimMissing, // SIM card is missing.
141 SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
142 SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
143 SimLocked, // SIM card is currently locked
144 SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
145 SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
146 SimIoError, // SIM card is faulty
147 SimUnknown // SIM card is unknown
148 }
149
150 /**
151 * Controller that provides updates on text with carriers names or SIM status.
152 * Used by {@link CarrierText}.
Fabian Kozynski1823f112019-01-18 11:43:29 -0500153 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500154 * @param separator Separator between different parts of the text
Fabian Kozynski02941af2019-01-17 17:57:37 -0500155 */
156 public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
157 boolean showMissingSim) {
158 mContext = context;
159 mIsEmergencyCallCapable = context.getResources().getBoolean(
160 com.android.internal.R.bool.config_voice_capable);
161
162 mShowAirplaneMode = showAirplaneMode;
163 mShowMissingSim = showMissingSim;
164
165 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
166 mSeparator = separator;
167 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500168 mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
169 Context.TELEPHONY_SERVICE)).getPhoneCount();
170 mSimErrorState = new boolean[mSimSlotsNumber];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500171 }
172
173 /**
174 * Checks if there are faulty cards. Adds the text depending on the slot of the card
175 *
176 * @param text: current carrier text based on the sim state
Fabian Kozynskib176f422019-02-05 09:36:59 -0500177 * @param carrierNames names order by subscription order
178 * @param subOrderBySlot array containing the sub index for each slot ID
Fabian Kozynski02941af2019-01-17 17:57:37 -0500179 * @param noSims: whether a valid sim card is inserted
180 * @return text
181 */
Fabian Kozynskib176f422019-02-05 09:36:59 -0500182 private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
183 CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500184 final CharSequence carrier = "";
185 CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
186 IccCardConstants.State.CARD_IO_ERROR, carrier);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500187 // mSimErrorState has the state of each sim indexed by slotID.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500188 for (int index = 0; index < mSimErrorState.length; index++) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500189 if (!mSimErrorState[index]) {
190 continue;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500191 }
Fabian Kozynskib176f422019-02-05 09:36:59 -0500192 // In the case when no sim cards are detected but a faulty card is inserted
193 // overwrite the text and only show "Invalid card"
194 if (noSims) {
195 return concatenate(carrierTextForSimIOError,
196 getContext().getText(
197 com.android.internal.R.string.emergency_calls_only),
198 mSeparator);
199 } else if (subOrderBySlot[index] != -1) {
200 int subIndex = subOrderBySlot[index];
201 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
202 carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
203 carrierNames[subIndex],
204 mSeparator);
205 } else {
206 // concatenate "Invalid card" when faulty card is inserted in other slot
207 text = concatenate(text, carrierTextForSimIOError, mSeparator);
208 }
209
Fabian Kozynski02941af2019-01-17 17:57:37 -0500210 }
211 return text;
212 }
213
214 /**
215 * Sets the listening status of this controller. If the callback is null, it is set to
216 * not listening
Fabian Kozynski1823f112019-01-18 11:43:29 -0500217 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500218 * @param callback Callback to provide text updates
219 */
220 public void setListening(CarrierTextCallback callback) {
Malcolm Chen06ec3572019-04-09 15:55:29 -0700221 TelephonyManager telephonyManager = ((TelephonyManager) mContext
222 .getSystemService(Context.TELEPHONY_SERVICE));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500223 if (callback != null) {
224 mCarrierTextCallback = callback;
Lucas Dupin8968f6a2019-08-09 17:41:15 -0700225 // TODO(b/140034799)
226 if (whitelistIpcs(() -> ConnectivityManager.from(mContext).isNetworkSupported(
227 ConnectivityManager.TYPE_MOBILE))) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500228 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
229 mKeyguardUpdateMonitor.registerCallback(mCallback);
230 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
Malcolm Chen06ec3572019-04-09 15:55:29 -0700231 telephonyManager.listen(mPhoneStateListener,
232 LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500233 } else {
234 // Don't listen and clear out the text when the device isn't a phone.
235 mKeyguardUpdateMonitor = null;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500236 callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500237 }
238 } else {
239 mCarrierTextCallback = null;
240 if (mKeyguardUpdateMonitor != null) {
241 mKeyguardUpdateMonitor.removeCallback(mCallback);
242 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
243 }
Malcolm Chen06ec3572019-04-09 15:55:29 -0700244 telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
245 }
246 }
247
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700248 protected List<SubscriptionInfo> getSubscriptionInfo() {
Malcolm Chen5c63b512019-08-13 13:24:07 -0700249 return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700250 }
251
Fabian Kozynski02941af2019-01-17 17:57:37 -0500252 protected void updateCarrierText() {
253 boolean allSimsMissing = true;
254 boolean anySimReadyAndInService = false;
255 CharSequence displayText = null;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700256 List<SubscriptionInfo> subs = getSubscriptionInfo();
Malcolm Chen06ec3572019-04-09 15:55:29 -0700257
Fabian Kozynski02941af2019-01-17 17:57:37 -0500258 final int numSubs = subs.size();
Fabian Kozynski1823f112019-01-18 11:43:29 -0500259 final int[] subsIds = new int[numSubs];
Fabian Kozynskib176f422019-02-05 09:36:59 -0500260 // This array will contain in position i, the index of subscription in slot ID i.
261 // -1 if no subscription in that slot
262 final int[] subOrderBySlot = new int[mSimSlotsNumber];
263 for (int i = 0; i < mSimSlotsNumber; i++) {
264 subOrderBySlot[i] = -1;
265 }
266 final CharSequence[] carrierNames = new CharSequence[numSubs];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500267 if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500268
Fabian Kozynski02941af2019-01-17 17:57:37 -0500269 for (int i = 0; i < numSubs; i++) {
270 int subId = subs.get(i).getSubscriptionId();
Fabian Kozynskib176f422019-02-05 09:36:59 -0500271 carrierNames[i] = "";
Fabian Kozynski1823f112019-01-18 11:43:29 -0500272 subsIds[i] = subId;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500273 subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500274 IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
275 CharSequence carrierName = subs.get(i).getCarrierName();
276 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
277 if (DEBUG) {
278 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
279 }
280 if (carrierTextForSimState != null) {
281 allSimsMissing = false;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500282 carrierNames[i] = carrierTextForSimState;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500283 }
284 if (simState == IccCardConstants.State.READY) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000285 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
286 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500287 // hack for WFC (IWLAN) not turning off immediately once
288 // Wi-Fi is disassociated or disabled
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000289 if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
Fabian Kozynski02941af2019-01-17 17:57:37 -0500290 || (mWifiManager.isWifiEnabled()
291 && mWifiManager.getConnectionInfo() != null
292 && mWifiManager.getConnectionInfo().getBSSID() != null)) {
293 if (DEBUG) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000294 Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500295 }
296 anySimReadyAndInService = true;
297 }
298 }
299 }
300 }
301 if (allSimsMissing) {
302 if (numSubs != 0) {
303 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
304 // This depends on mPlmn containing the text "Emergency calls only" when the radio
305 // has some connectivity. Otherwise, it should be null or empty and just show
306 // "No SIM card"
307 // Grab the first subscripton, because they all should contain the emergency text,
308 // described above.
309 displayText = makeCarrierStringOnEmergencyCapable(
310 getMissingSimMessage(), subs.get(0).getCarrierName());
311 } else {
312 // We don't have a SubscriptionInfo to get the emergency calls only from.
313 // Grab it from the old sticky broadcast if possible instead. We can use it
314 // here because no subscriptions are active, so we don't have
315 // to worry about MSIM clashing.
316 CharSequence text =
317 getContext().getText(com.android.internal.R.string.emergency_calls_only);
318 Intent i = getContext().registerReceiver(null,
319 new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
320 if (i != null) {
321 String spn = "";
322 String plmn = "";
323 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
324 spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
325 }
326 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
327 plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
328 }
329 if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
330 if (Objects.equals(plmn, spn)) {
331 text = plmn;
332 } else {
333 text = concatenate(plmn, spn, mSeparator);
334 }
335 }
336 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
337 }
338 }
339
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400340 if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
341
Fabian Kozynskib176f422019-02-05 09:36:59 -0500342 displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
343 allSimsMissing);
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400344
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400345 boolean airplaneMode = false;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500346 // APM (airplane mode) != no carrier state. There are carrier services
347 // (e.g. WFC = Wi-Fi calling) which may operate in APM.
348 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
349 displayText = getAirplaneModeMessage();
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400350 airplaneMode = true;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500351 }
352
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500353 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
354 displayText,
Fabian Kozynskib176f422019-02-05 09:36:59 -0500355 carrierNames,
356 !allSimsMissing,
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400357 subsIds,
358 airplaneMode);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500359 postToCallback(info);
360 }
361
362 @VisibleForTesting
363 protected void postToCallback(CarrierTextCallbackInfo info) {
364 Handler handler = Dependency.get(Dependency.MAIN_HANDLER);
Fabian Kozynski48343c32019-02-07 10:28:50 -0500365 final CarrierTextCallback callback = mCarrierTextCallback;
366 if (callback != null) {
367 handler.post(() -> callback.updateCarrierInfo(info));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500368 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500369 }
370
371 private Context getContext() {
372 return mContext;
373 }
374
375 private String getMissingSimMessage() {
376 return mShowMissingSim && mTelephonyCapable
377 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
378 }
379
380 private String getAirplaneModeMessage() {
381 return mShowAirplaneMode
382 ? getContext().getString(R.string.airplane_mode) : "";
383 }
384
385 /**
386 * Top-level function for creating carrier text. Makes text based on simState, PLMN
387 * and SPN as well as device capabilities, such as being emergency call capable.
388 *
389 * @return Carrier text if not in missing state, null otherwise.
390 */
391 private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
392 CharSequence text) {
393 CharSequence carrierText = null;
394 CarrierTextController.StatusMode status = getStatusForIccState(simState);
395 switch (status) {
396 case Normal:
397 carrierText = text;
398 break;
399
400 case SimNotReady:
401 // Null is reserved for denoting missing, in this case we have nothing to display.
402 carrierText = ""; // nothing to display yet.
403 break;
404
405 case NetworkLocked:
406 carrierText = makeCarrierStringOnEmergencyCapable(
407 mContext.getText(R.string.keyguard_network_locked_message), text);
408 break;
409
410 case SimMissing:
411 carrierText = null;
412 break;
413
414 case SimPermDisabled:
415 carrierText = makeCarrierStringOnEmergencyCapable(
416 getContext().getText(
417 R.string.keyguard_permanent_disabled_sim_message_short),
418 text);
419 break;
420
421 case SimMissingLocked:
422 carrierText = null;
423 break;
424
425 case SimLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400426 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500427 getContext().getText(R.string.keyguard_sim_locked_message),
428 text);
429 break;
430
431 case SimPukLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400432 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500433 getContext().getText(R.string.keyguard_sim_puk_locked_message),
434 text);
435 break;
436 case SimIoError:
437 carrierText = makeCarrierStringOnEmergencyCapable(
438 getContext().getText(R.string.keyguard_sim_error_message_short),
439 text);
440 break;
441 case SimUnknown:
442 carrierText = null;
443 break;
444 }
445
446 return carrierText;
447 }
448
449 /*
450 * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
451 */
452 private CharSequence makeCarrierStringOnEmergencyCapable(
453 CharSequence simMessage, CharSequence emergencyCallMessage) {
454 if (mIsEmergencyCallCapable) {
455 return concatenate(simMessage, emergencyCallMessage, mSeparator);
456 }
457 return simMessage;
458 }
459
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400460 /*
461 * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
462 * DSDS
463 */
464 private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
465 CharSequence carrierName) {
466 final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
467 final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
468 if (simMessageValid && carrierNameValid) {
469 return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
470 carrierName, simMessage);
471 } else if (simMessageValid) {
472 return simMessage;
473 } else if (carrierNameValid) {
474 return carrierName;
475 } else {
476 return "";
477 }
478 }
479
Fabian Kozynski02941af2019-01-17 17:57:37 -0500480 /**
481 * Determine the current status of the lock screen given the SIM state and other stuff.
482 */
483 private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
484 // Since reading the SIM may take a while, we assume it is present until told otherwise.
485 if (simState == null) {
486 return CarrierTextController.StatusMode.Normal;
487 }
488
489 final boolean missingAndNotProvisioned =
490 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
491 && (simState == IccCardConstants.State.ABSENT
492 || simState == IccCardConstants.State.PERM_DISABLED);
493
494 // Assume we're NETWORK_LOCKED if not provisioned
495 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
496 switch (simState) {
497 case ABSENT:
498 return CarrierTextController.StatusMode.SimMissing;
499 case NETWORK_LOCKED:
500 return CarrierTextController.StatusMode.SimMissingLocked;
501 case NOT_READY:
502 return CarrierTextController.StatusMode.SimNotReady;
503 case PIN_REQUIRED:
504 return CarrierTextController.StatusMode.SimLocked;
505 case PUK_REQUIRED:
506 return CarrierTextController.StatusMode.SimPukLocked;
507 case READY:
508 return CarrierTextController.StatusMode.Normal;
509 case PERM_DISABLED:
510 return CarrierTextController.StatusMode.SimPermDisabled;
511 case UNKNOWN:
512 return CarrierTextController.StatusMode.SimUnknown;
513 case CARD_IO_ERROR:
514 return CarrierTextController.StatusMode.SimIoError;
515 }
516 return CarrierTextController.StatusMode.SimUnknown;
517 }
518
519 private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
520 CharSequence separator) {
521 final boolean plmnValid = !TextUtils.isEmpty(plmn);
522 final boolean spnValid = !TextUtils.isEmpty(spn);
523 if (plmnValid && spnValid) {
524 return new StringBuilder().append(plmn).append(separator).append(spn).toString();
525 } else if (plmnValid) {
526 return plmn;
527 } else if (spnValid) {
528 return spn;
529 } else {
530 return "";
531 }
532 }
533
Fabian Kozynski00d02f12019-04-15 09:48:30 -0400534 /**
535 * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
536 * separator added so there are no extra separators that are not needed.
537 */
538 private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
539 int length = sequences.length;
540 if (length == 0) return "";
541 StringBuilder sb = new StringBuilder();
542 for (int i = 0; i < length; i++) {
543 if (!TextUtils.isEmpty(sequences[i])) {
544 if (!TextUtils.isEmpty(sb)) {
545 sb.append(separator);
546 }
547 sb.append(sequences[i]);
548 }
549 }
550 return sb.toString();
551 }
552
Fabian Kozynski02941af2019-01-17 17:57:37 -0500553 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
554 if (!TextUtils.isEmpty(string)) {
555 list.add(string);
556 }
557 return list;
558 }
559
560 private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
561 String plmn, String spn) {
562 int carrierHelpTextId = 0;
563 CarrierTextController.StatusMode status = getStatusForIccState(simState);
564 switch (status) {
565 case NetworkLocked:
566 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
567 break;
568
569 case SimMissing:
570 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
571 break;
572
573 case SimPermDisabled:
574 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
575 break;
576
577 case SimMissingLocked:
578 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
579 break;
580
581 case Normal:
582 case SimLocked:
583 case SimPukLocked:
584 break;
585 }
586
587 return mContext.getText(carrierHelpTextId);
588 }
589
590 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500591 * Data structure for passing information to CarrierTextController subscribers
592 */
593 public static final class CarrierTextCallbackInfo {
594 public final CharSequence carrierText;
595 public final CharSequence[] listOfCarriers;
596 public final boolean anySimReady;
597 public final int[] subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400598 public boolean airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500599
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500600 @VisibleForTesting
601 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
Fabian Kozynski1823f112019-01-18 11:43:29 -0500602 boolean anySimReady, int[] subscriptionIds) {
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400603 this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
604 }
605
606 @VisibleForTesting
607 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
608 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
Fabian Kozynski1823f112019-01-18 11:43:29 -0500609 this.carrierText = carrierText;
610 this.listOfCarriers = listOfCarriers;
611 this.anySimReady = anySimReady;
612 this.subscriptionIds = subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400613 this.airplaneMode = airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500614 }
615 }
616
617 /**
Fabian Kozynski02941af2019-01-17 17:57:37 -0500618 * Callback to communicate to Views
619 */
620 public interface CarrierTextCallback {
621 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500622 * Provides updated carrier information.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500623 */
Fabian Kozynski1823f112019-01-18 11:43:29 -0500624 default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
Fabian Kozynski02941af2019-01-17 17:57:37 -0500625
626 /**
627 * Notifies the View that the device is going to sleep
628 */
629 default void startedGoingToSleep() {};
630
631 /**
632 * Notifies the View that the device finished waking up
633 */
634 default void finishedWakingUp() {};
635 }
636}