blob: 45126f35d8642f52ff5742005d7c6fb7631b5c72 [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
Fabian Kozynski02941af2019-01-17 17:57:37 -050022import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.net.ConnectivityManager;
26import android.net.wifi.WifiManager;
Fabian Kozynskibf6fef32019-02-04 09:21:38 -050027import android.os.Handler;
Malcolm Chen06ec3572019-04-09 15:55:29 -070028import android.telephony.PhoneStateListener;
Fabian Kozynski02941af2019-01-17 17:57:37 -050029import android.telephony.ServiceState;
30import android.telephony.SubscriptionInfo;
Malcolm Chen06ec3572019-04-09 15:55:29 -070031import android.telephony.SubscriptionManager;
Fabian Kozynski02941af2019-01-17 17:57:37 -050032import android.telephony.TelephonyManager;
33import android.text.TextUtils;
34import android.util.Log;
35
36import com.android.internal.telephony.IccCardConstants;
37import com.android.internal.telephony.TelephonyIntents;
38import com.android.settingslib.WirelessUtils;
39import com.android.systemui.Dependency;
40import com.android.systemui.keyguard.WakefulnessLifecycle;
41
42import java.util.List;
43import java.util.Objects;
44
Malcolm Chen5c63b512019-08-13 13:24:07 -070045import androidx.annotation.VisibleForTesting;
46
Fabian Kozynski02941af2019-01-17 17:57:37 -050047/**
48 * Controller that generates text including the carrier names and/or the status of all the SIM
49 * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
50 * separated by a given separator {@link CharSequence}.
51 */
52public class CarrierTextController {
53 private static final boolean DEBUG = KeyguardConstants.DEBUG;
54 private static final String TAG = "CarrierTextController";
55
56 private final boolean mIsEmergencyCallCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050057 private boolean mTelephonyCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050058 private boolean mShowMissingSim;
Fabian Kozynski02941af2019-01-17 17:57:37 -050059 private boolean mShowAirplaneMode;
Fabian Kozynskib176f422019-02-05 09:36:59 -050060 @VisibleForTesting
61 protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
Fabian Kozynski02941af2019-01-17 17:57:37 -050062 private WifiManager mWifiManager;
Fabian Kozynskib176f422019-02-05 09:36:59 -050063 private boolean[] mSimErrorState;
64 private final int mSimSlotsNumber;
Fabian Kozynski02941af2019-01-17 17:57:37 -050065 private CarrierTextCallback mCarrierTextCallback;
66 private Context mContext;
67 private CharSequence mSeparator;
68 private WakefulnessLifecycle mWakefulnessLifecycle;
69 private final WakefulnessLifecycle.Observer mWakefulnessObserver =
70 new WakefulnessLifecycle.Observer() {
71 @Override
72 public void onFinishedWakingUp() {
73 mCarrierTextCallback.finishedWakingUp();
74 }
75
76 @Override
77 public void onStartedGoingToSleep() {
78 mCarrierTextCallback.startedGoingToSleep();
79 }
80 };
81
Fabian Kozynskib176f422019-02-05 09:36:59 -050082 @VisibleForTesting
83 protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
Fabian Kozynski02941af2019-01-17 17:57:37 -050084 @Override
85 public void onRefreshCarrierInfo() {
86 if (DEBUG) {
87 Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
88 + Boolean.toString(mTelephonyCapable));
89 }
90 updateCarrierText();
91 }
92
93 @Override
94 public void onTelephonyCapable(boolean capable) {
95 if (DEBUG) {
96 Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
97 + Boolean.toString(capable));
98 }
99 mTelephonyCapable = capable;
100 updateCarrierText();
101 }
102
103 public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500104 if (slotId < 0 || slotId >= mSimSlotsNumber) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500105 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
106 + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
107 return;
108 }
109
110 if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
111 if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
112 mSimErrorState[slotId] = true;
113 updateCarrierText();
114 } else if (mSimErrorState[slotId]) {
115 mSimErrorState[slotId] = false;
116 updateCarrierText();
117 }
118 }
119 };
120
Malcolm Chen06ec3572019-04-09 15:55:29 -0700121 private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
122 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
123 @Override
124 public void onActiveDataSubscriptionIdChanged(int subId) {
125 mActiveMobileDataSubscription = subId;
126 if (mKeyguardUpdateMonitor != null) {
127 updateCarrierText();
128 }
129 }
130 };
131
Fabian Kozynski02941af2019-01-17 17:57:37 -0500132 /**
133 * The status of this lock screen. Primarily used for widgets on LockScreen.
134 */
135 private enum StatusMode {
136 Normal, // Normal case (sim card present, it's not locked)
137 NetworkLocked, // SIM card is 'network locked'.
138 SimMissing, // SIM card is missing.
139 SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
140 SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
141 SimLocked, // SIM card is currently locked
142 SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
143 SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
144 SimIoError, // SIM card is faulty
145 SimUnknown // SIM card is unknown
146 }
147
148 /**
149 * Controller that provides updates on text with carriers names or SIM status.
150 * Used by {@link CarrierText}.
Fabian Kozynski1823f112019-01-18 11:43:29 -0500151 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500152 * @param separator Separator between different parts of the text
Fabian Kozynski02941af2019-01-17 17:57:37 -0500153 */
154 public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
155 boolean showMissingSim) {
156 mContext = context;
157 mIsEmergencyCallCapable = context.getResources().getBoolean(
158 com.android.internal.R.bool.config_voice_capable);
159
160 mShowAirplaneMode = showAirplaneMode;
161 mShowMissingSim = showMissingSim;
162
163 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
164 mSeparator = separator;
165 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500166 mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
167 Context.TELEPHONY_SERVICE)).getPhoneCount();
168 mSimErrorState = new boolean[mSimSlotsNumber];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500169 }
170
171 /**
172 * Checks if there are faulty cards. Adds the text depending on the slot of the card
173 *
174 * @param text: current carrier text based on the sim state
Fabian Kozynskib176f422019-02-05 09:36:59 -0500175 * @param carrierNames names order by subscription order
176 * @param subOrderBySlot array containing the sub index for each slot ID
Fabian Kozynski02941af2019-01-17 17:57:37 -0500177 * @param noSims: whether a valid sim card is inserted
178 * @return text
179 */
Fabian Kozynskib176f422019-02-05 09:36:59 -0500180 private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
181 CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500182 final CharSequence carrier = "";
183 CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
184 IccCardConstants.State.CARD_IO_ERROR, carrier);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500185 // mSimErrorState has the state of each sim indexed by slotID.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500186 for (int index = 0; index < mSimErrorState.length; index++) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500187 if (!mSimErrorState[index]) {
188 continue;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500189 }
Fabian Kozynskib176f422019-02-05 09:36:59 -0500190 // In the case when no sim cards are detected but a faulty card is inserted
191 // overwrite the text and only show "Invalid card"
192 if (noSims) {
193 return concatenate(carrierTextForSimIOError,
194 getContext().getText(
195 com.android.internal.R.string.emergency_calls_only),
196 mSeparator);
197 } else if (subOrderBySlot[index] != -1) {
198 int subIndex = subOrderBySlot[index];
199 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
200 carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
201 carrierNames[subIndex],
202 mSeparator);
203 } else {
204 // concatenate "Invalid card" when faulty card is inserted in other slot
205 text = concatenate(text, carrierTextForSimIOError, mSeparator);
206 }
207
Fabian Kozynski02941af2019-01-17 17:57:37 -0500208 }
209 return text;
210 }
211
212 /**
213 * Sets the listening status of this controller. If the callback is null, it is set to
214 * not listening
Fabian Kozynski1823f112019-01-18 11:43:29 -0500215 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500216 * @param callback Callback to provide text updates
217 */
218 public void setListening(CarrierTextCallback callback) {
Malcolm Chen06ec3572019-04-09 15:55:29 -0700219 TelephonyManager telephonyManager = ((TelephonyManager) mContext
220 .getSystemService(Context.TELEPHONY_SERVICE));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500221 if (callback != null) {
222 mCarrierTextCallback = callback;
223 if (ConnectivityManager.from(mContext).isNetworkSupported(
224 ConnectivityManager.TYPE_MOBILE)) {
225 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
226 mKeyguardUpdateMonitor.registerCallback(mCallback);
227 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
Malcolm Chen06ec3572019-04-09 15:55:29 -0700228 telephonyManager.listen(mPhoneStateListener,
229 LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500230 } else {
231 // Don't listen and clear out the text when the device isn't a phone.
232 mKeyguardUpdateMonitor = null;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500233 callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500234 }
235 } else {
236 mCarrierTextCallback = null;
237 if (mKeyguardUpdateMonitor != null) {
238 mKeyguardUpdateMonitor.removeCallback(mCallback);
239 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
240 }
Malcolm Chen06ec3572019-04-09 15:55:29 -0700241 telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
242 }
243 }
244
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700245 protected List<SubscriptionInfo> getSubscriptionInfo() {
Malcolm Chen5c63b512019-08-13 13:24:07 -0700246 return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700247 }
248
Fabian Kozynski02941af2019-01-17 17:57:37 -0500249 protected void updateCarrierText() {
250 boolean allSimsMissing = true;
251 boolean anySimReadyAndInService = false;
252 CharSequence displayText = null;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700253 List<SubscriptionInfo> subs = getSubscriptionInfo();
Malcolm Chen06ec3572019-04-09 15:55:29 -0700254
Fabian Kozynski02941af2019-01-17 17:57:37 -0500255 final int numSubs = subs.size();
Fabian Kozynski1823f112019-01-18 11:43:29 -0500256 final int[] subsIds = new int[numSubs];
Fabian Kozynskib176f422019-02-05 09:36:59 -0500257 // This array will contain in position i, the index of subscription in slot ID i.
258 // -1 if no subscription in that slot
259 final int[] subOrderBySlot = new int[mSimSlotsNumber];
260 for (int i = 0; i < mSimSlotsNumber; i++) {
261 subOrderBySlot[i] = -1;
262 }
263 final CharSequence[] carrierNames = new CharSequence[numSubs];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500264 if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500265
Fabian Kozynski02941af2019-01-17 17:57:37 -0500266 for (int i = 0; i < numSubs; i++) {
267 int subId = subs.get(i).getSubscriptionId();
Fabian Kozynskib176f422019-02-05 09:36:59 -0500268 carrierNames[i] = "";
Fabian Kozynski1823f112019-01-18 11:43:29 -0500269 subsIds[i] = subId;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500270 subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500271 IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
272 CharSequence carrierName = subs.get(i).getCarrierName();
273 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
274 if (DEBUG) {
275 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
276 }
277 if (carrierTextForSimState != null) {
278 allSimsMissing = false;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500279 carrierNames[i] = carrierTextForSimState;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500280 }
281 if (simState == IccCardConstants.State.READY) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000282 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
283 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500284 // hack for WFC (IWLAN) not turning off immediately once
285 // Wi-Fi is disassociated or disabled
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000286 if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
Fabian Kozynski02941af2019-01-17 17:57:37 -0500287 || (mWifiManager.isWifiEnabled()
288 && mWifiManager.getConnectionInfo() != null
289 && mWifiManager.getConnectionInfo().getBSSID() != null)) {
290 if (DEBUG) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000291 Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500292 }
293 anySimReadyAndInService = true;
294 }
295 }
296 }
297 }
298 if (allSimsMissing) {
299 if (numSubs != 0) {
300 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
301 // This depends on mPlmn containing the text "Emergency calls only" when the radio
302 // has some connectivity. Otherwise, it should be null or empty and just show
303 // "No SIM card"
304 // Grab the first subscripton, because they all should contain the emergency text,
305 // described above.
306 displayText = makeCarrierStringOnEmergencyCapable(
307 getMissingSimMessage(), subs.get(0).getCarrierName());
308 } else {
309 // We don't have a SubscriptionInfo to get the emergency calls only from.
310 // Grab it from the old sticky broadcast if possible instead. We can use it
311 // here because no subscriptions are active, so we don't have
312 // to worry about MSIM clashing.
313 CharSequence text =
314 getContext().getText(com.android.internal.R.string.emergency_calls_only);
315 Intent i = getContext().registerReceiver(null,
316 new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
317 if (i != null) {
318 String spn = "";
319 String plmn = "";
320 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
321 spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
322 }
323 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
324 plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
325 }
326 if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
327 if (Objects.equals(plmn, spn)) {
328 text = plmn;
329 } else {
330 text = concatenate(plmn, spn, mSeparator);
331 }
332 }
333 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
334 }
335 }
336
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400337 if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
338
Fabian Kozynskib176f422019-02-05 09:36:59 -0500339 displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
340 allSimsMissing);
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400341
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400342 boolean airplaneMode = false;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500343 // APM (airplane mode) != no carrier state. There are carrier services
344 // (e.g. WFC = Wi-Fi calling) which may operate in APM.
345 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
346 displayText = getAirplaneModeMessage();
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400347 airplaneMode = true;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500348 }
349
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500350 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
351 displayText,
Fabian Kozynskib176f422019-02-05 09:36:59 -0500352 carrierNames,
353 !allSimsMissing,
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400354 subsIds,
355 airplaneMode);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500356 postToCallback(info);
357 }
358
359 @VisibleForTesting
360 protected void postToCallback(CarrierTextCallbackInfo info) {
361 Handler handler = Dependency.get(Dependency.MAIN_HANDLER);
Fabian Kozynski48343c32019-02-07 10:28:50 -0500362 final CarrierTextCallback callback = mCarrierTextCallback;
363 if (callback != null) {
364 handler.post(() -> callback.updateCarrierInfo(info));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500365 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500366 }
367
368 private Context getContext() {
369 return mContext;
370 }
371
372 private String getMissingSimMessage() {
373 return mShowMissingSim && mTelephonyCapable
374 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
375 }
376
377 private String getAirplaneModeMessage() {
378 return mShowAirplaneMode
379 ? getContext().getString(R.string.airplane_mode) : "";
380 }
381
382 /**
383 * Top-level function for creating carrier text. Makes text based on simState, PLMN
384 * and SPN as well as device capabilities, such as being emergency call capable.
385 *
386 * @return Carrier text if not in missing state, null otherwise.
387 */
388 private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
389 CharSequence text) {
390 CharSequence carrierText = null;
391 CarrierTextController.StatusMode status = getStatusForIccState(simState);
392 switch (status) {
393 case Normal:
394 carrierText = text;
395 break;
396
397 case SimNotReady:
398 // Null is reserved for denoting missing, in this case we have nothing to display.
399 carrierText = ""; // nothing to display yet.
400 break;
401
402 case NetworkLocked:
403 carrierText = makeCarrierStringOnEmergencyCapable(
404 mContext.getText(R.string.keyguard_network_locked_message), text);
405 break;
406
407 case SimMissing:
408 carrierText = null;
409 break;
410
411 case SimPermDisabled:
412 carrierText = makeCarrierStringOnEmergencyCapable(
413 getContext().getText(
414 R.string.keyguard_permanent_disabled_sim_message_short),
415 text);
416 break;
417
418 case SimMissingLocked:
419 carrierText = null;
420 break;
421
422 case SimLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400423 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500424 getContext().getText(R.string.keyguard_sim_locked_message),
425 text);
426 break;
427
428 case SimPukLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400429 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500430 getContext().getText(R.string.keyguard_sim_puk_locked_message),
431 text);
432 break;
433 case SimIoError:
434 carrierText = makeCarrierStringOnEmergencyCapable(
435 getContext().getText(R.string.keyguard_sim_error_message_short),
436 text);
437 break;
438 case SimUnknown:
439 carrierText = null;
440 break;
441 }
442
443 return carrierText;
444 }
445
446 /*
447 * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
448 */
449 private CharSequence makeCarrierStringOnEmergencyCapable(
450 CharSequence simMessage, CharSequence emergencyCallMessage) {
451 if (mIsEmergencyCallCapable) {
452 return concatenate(simMessage, emergencyCallMessage, mSeparator);
453 }
454 return simMessage;
455 }
456
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400457 /*
458 * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
459 * DSDS
460 */
461 private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
462 CharSequence carrierName) {
463 final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
464 final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
465 if (simMessageValid && carrierNameValid) {
466 return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
467 carrierName, simMessage);
468 } else if (simMessageValid) {
469 return simMessage;
470 } else if (carrierNameValid) {
471 return carrierName;
472 } else {
473 return "";
474 }
475 }
476
Fabian Kozynski02941af2019-01-17 17:57:37 -0500477 /**
478 * Determine the current status of the lock screen given the SIM state and other stuff.
479 */
480 private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
481 // Since reading the SIM may take a while, we assume it is present until told otherwise.
482 if (simState == null) {
483 return CarrierTextController.StatusMode.Normal;
484 }
485
486 final boolean missingAndNotProvisioned =
487 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
488 && (simState == IccCardConstants.State.ABSENT
489 || simState == IccCardConstants.State.PERM_DISABLED);
490
491 // Assume we're NETWORK_LOCKED if not provisioned
492 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
493 switch (simState) {
494 case ABSENT:
495 return CarrierTextController.StatusMode.SimMissing;
496 case NETWORK_LOCKED:
497 return CarrierTextController.StatusMode.SimMissingLocked;
498 case NOT_READY:
499 return CarrierTextController.StatusMode.SimNotReady;
500 case PIN_REQUIRED:
501 return CarrierTextController.StatusMode.SimLocked;
502 case PUK_REQUIRED:
503 return CarrierTextController.StatusMode.SimPukLocked;
504 case READY:
505 return CarrierTextController.StatusMode.Normal;
506 case PERM_DISABLED:
507 return CarrierTextController.StatusMode.SimPermDisabled;
508 case UNKNOWN:
509 return CarrierTextController.StatusMode.SimUnknown;
510 case CARD_IO_ERROR:
511 return CarrierTextController.StatusMode.SimIoError;
512 }
513 return CarrierTextController.StatusMode.SimUnknown;
514 }
515
516 private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
517 CharSequence separator) {
518 final boolean plmnValid = !TextUtils.isEmpty(plmn);
519 final boolean spnValid = !TextUtils.isEmpty(spn);
520 if (plmnValid && spnValid) {
521 return new StringBuilder().append(plmn).append(separator).append(spn).toString();
522 } else if (plmnValid) {
523 return plmn;
524 } else if (spnValid) {
525 return spn;
526 } else {
527 return "";
528 }
529 }
530
Fabian Kozynski00d02f12019-04-15 09:48:30 -0400531 /**
532 * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
533 * separator added so there are no extra separators that are not needed.
534 */
535 private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
536 int length = sequences.length;
537 if (length == 0) return "";
538 StringBuilder sb = new StringBuilder();
539 for (int i = 0; i < length; i++) {
540 if (!TextUtils.isEmpty(sequences[i])) {
541 if (!TextUtils.isEmpty(sb)) {
542 sb.append(separator);
543 }
544 sb.append(sequences[i]);
545 }
546 }
547 return sb.toString();
548 }
549
Fabian Kozynski02941af2019-01-17 17:57:37 -0500550 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
551 if (!TextUtils.isEmpty(string)) {
552 list.add(string);
553 }
554 return list;
555 }
556
557 private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
558 String plmn, String spn) {
559 int carrierHelpTextId = 0;
560 CarrierTextController.StatusMode status = getStatusForIccState(simState);
561 switch (status) {
562 case NetworkLocked:
563 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
564 break;
565
566 case SimMissing:
567 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
568 break;
569
570 case SimPermDisabled:
571 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
572 break;
573
574 case SimMissingLocked:
575 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
576 break;
577
578 case Normal:
579 case SimLocked:
580 case SimPukLocked:
581 break;
582 }
583
584 return mContext.getText(carrierHelpTextId);
585 }
586
587 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500588 * Data structure for passing information to CarrierTextController subscribers
589 */
590 public static final class CarrierTextCallbackInfo {
591 public final CharSequence carrierText;
592 public final CharSequence[] listOfCarriers;
593 public final boolean anySimReady;
594 public final int[] subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400595 public boolean airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500596
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500597 @VisibleForTesting
598 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
Fabian Kozynski1823f112019-01-18 11:43:29 -0500599 boolean anySimReady, int[] subscriptionIds) {
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400600 this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
601 }
602
603 @VisibleForTesting
604 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
605 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
Fabian Kozynski1823f112019-01-18 11:43:29 -0500606 this.carrierText = carrierText;
607 this.listOfCarriers = listOfCarriers;
608 this.anySimReady = anySimReady;
609 this.subscriptionIds = subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400610 this.airplaneMode = airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500611 }
612 }
613
614 /**
Fabian Kozynski02941af2019-01-17 17:57:37 -0500615 * Callback to communicate to Views
616 */
617 public interface CarrierTextCallback {
618 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500619 * Provides updated carrier information.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500620 */
Fabian Kozynski1823f112019-01-18 11:43:29 -0500621 default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
Fabian Kozynski02941af2019-01-17 17:57:37 -0500622
623 /**
624 * Notifies the View that the device is going to sleep
625 */
626 default void startedGoingToSleep() {};
627
628 /**
629 * Notifies the View that the device finished waking up
630 */
631 default void finishedWakingUp() {};
632 }
633}