blob: 2f4406b5cc11ec883bffb00afe1f07ef558b157f [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;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -070030import android.os.SystemProperties;
Malcolm Chen06ec3572019-04-09 15:55:29 -070031import android.telephony.CarrierConfigManager;
32import android.telephony.PhoneStateListener;
Fabian Kozynski02941af2019-01-17 17:57:37 -050033import android.telephony.ServiceState;
34import android.telephony.SubscriptionInfo;
Malcolm Chen06ec3572019-04-09 15:55:29 -070035import android.telephony.SubscriptionManager;
Fabian Kozynski02941af2019-01-17 17:57:37 -050036import android.telephony.TelephonyManager;
37import android.text.TextUtils;
38import android.util.Log;
39
Fabian Kozynskibf6fef32019-02-04 09:21:38 -050040import androidx.annotation.VisibleForTesting;
41
Fabian Kozynski02941af2019-01-17 17:57:37 -050042import com.android.internal.telephony.IccCardConstants;
43import com.android.internal.telephony.TelephonyIntents;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -070044import com.android.internal.telephony.TelephonyProperties;
Fabian Kozynski02941af2019-01-17 17:57:37 -050045import com.android.settingslib.WirelessUtils;
46import com.android.systemui.Dependency;
47import com.android.systemui.keyguard.WakefulnessLifecycle;
48
Malcolm Chen06ec3572019-04-09 15:55:29 -070049import java.util.ArrayList;
Fabian Kozynski02941af2019-01-17 17:57:37 -050050import java.util.List;
51import java.util.Objects;
52
53/**
54 * Controller that generates text including the carrier names and/or the status of all the SIM
55 * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
56 * separated by a given separator {@link CharSequence}.
57 */
58public class CarrierTextController {
59 private static final boolean DEBUG = KeyguardConstants.DEBUG;
60 private static final String TAG = "CarrierTextController";
61
62 private final boolean mIsEmergencyCallCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050063 private boolean mTelephonyCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050064 private boolean mShowMissingSim;
Fabian Kozynski02941af2019-01-17 17:57:37 -050065 private boolean mShowAirplaneMode;
Fabian Kozynskib176f422019-02-05 09:36:59 -050066 @VisibleForTesting
67 protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
Fabian Kozynski02941af2019-01-17 17:57:37 -050068 private WifiManager mWifiManager;
Fabian Kozynskib176f422019-02-05 09:36:59 -050069 private boolean[] mSimErrorState;
70 private final int mSimSlotsNumber;
Fabian Kozynski02941af2019-01-17 17:57:37 -050071 private CarrierTextCallback mCarrierTextCallback;
72 private Context mContext;
73 private CharSequence mSeparator;
74 private WakefulnessLifecycle mWakefulnessLifecycle;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -070075 @VisibleForTesting
76 protected boolean mDisplayOpportunisticSubscriptionCarrierText;
Fabian Kozynski02941af2019-01-17 17:57:37 -050077 private final WakefulnessLifecycle.Observer mWakefulnessObserver =
78 new WakefulnessLifecycle.Observer() {
79 @Override
80 public void onFinishedWakingUp() {
81 mCarrierTextCallback.finishedWakingUp();
82 }
83
84 @Override
85 public void onStartedGoingToSleep() {
86 mCarrierTextCallback.startedGoingToSleep();
87 }
88 };
89
Fabian Kozynskib176f422019-02-05 09:36:59 -050090 @VisibleForTesting
91 protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
Fabian Kozynski02941af2019-01-17 17:57:37 -050092 @Override
93 public void onRefreshCarrierInfo() {
94 if (DEBUG) {
95 Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
96 + Boolean.toString(mTelephonyCapable));
97 }
98 updateCarrierText();
99 }
100
101 @Override
102 public void onTelephonyCapable(boolean capable) {
103 if (DEBUG) {
104 Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
105 + Boolean.toString(capable));
106 }
107 mTelephonyCapable = capable;
108 updateCarrierText();
109 }
110
111 public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500112 if (slotId < 0 || slotId >= mSimSlotsNumber) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500113 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
114 + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
115 return;
116 }
117
118 if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
119 if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
120 mSimErrorState[slotId] = true;
121 updateCarrierText();
122 } else if (mSimErrorState[slotId]) {
123 mSimErrorState[slotId] = false;
124 updateCarrierText();
125 }
126 }
127 };
128
Malcolm Chen06ec3572019-04-09 15:55:29 -0700129 private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
130 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
131 @Override
132 public void onActiveDataSubscriptionIdChanged(int subId) {
133 mActiveMobileDataSubscription = subId;
134 if (mKeyguardUpdateMonitor != null) {
135 updateCarrierText();
136 }
137 }
138 };
139
Fabian Kozynski02941af2019-01-17 17:57:37 -0500140 /**
141 * The status of this lock screen. Primarily used for widgets on LockScreen.
142 */
143 private enum StatusMode {
144 Normal, // Normal case (sim card present, it's not locked)
145 NetworkLocked, // SIM card is 'network locked'.
146 SimMissing, // SIM card is missing.
147 SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
148 SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
149 SimLocked, // SIM card is currently locked
150 SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
151 SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
152 SimIoError, // SIM card is faulty
153 SimUnknown // SIM card is unknown
154 }
155
156 /**
157 * Controller that provides updates on text with carriers names or SIM status.
158 * Used by {@link CarrierText}.
Fabian Kozynski1823f112019-01-18 11:43:29 -0500159 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500160 * @param separator Separator between different parts of the text
Fabian Kozynski02941af2019-01-17 17:57:37 -0500161 */
162 public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
163 boolean showMissingSim) {
164 mContext = context;
165 mIsEmergencyCallCapable = context.getResources().getBoolean(
166 com.android.internal.R.bool.config_voice_capable);
167
168 mShowAirplaneMode = showAirplaneMode;
169 mShowMissingSim = showMissingSim;
170
171 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
172 mSeparator = separator;
173 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
Malcolm Chend3844352019-11-04 16:26:28 -0800174 mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
Fabian Kozynskib176f422019-02-05 09:36:59 -0500175 mSimErrorState = new boolean[mSimSlotsNumber];
Sooraj Sasindranb7d633b2019-05-02 13:47:21 -0700176 updateDisplayOpportunisticSubscriptionCarrierText(SystemProperties.getBoolean(
177 TelephonyProperties.DISPLAY_OPPORTUNISTIC_SUBSCRIPTION_CARRIER_TEXT_PROPERTY_NAME,
178 false));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500179 }
180
Malcolm Chend3844352019-11-04 16:26:28 -0800181 private TelephonyManager getTelephonyManager() {
182 return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
183 }
184
Fabian Kozynski02941af2019-01-17 17:57:37 -0500185 /**
186 * Checks if there are faulty cards. Adds the text depending on the slot of the card
187 *
188 * @param text: current carrier text based on the sim state
Fabian Kozynskib176f422019-02-05 09:36:59 -0500189 * @param carrierNames names order by subscription order
190 * @param subOrderBySlot array containing the sub index for each slot ID
Fabian Kozynski02941af2019-01-17 17:57:37 -0500191 * @param noSims: whether a valid sim card is inserted
192 * @return text
193 */
Fabian Kozynskib176f422019-02-05 09:36:59 -0500194 private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
195 CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500196 final CharSequence carrier = "";
197 CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
198 IccCardConstants.State.CARD_IO_ERROR, carrier);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500199 // mSimErrorState has the state of each sim indexed by slotID.
Malcolm Chend3844352019-11-04 16:26:28 -0800200 for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500201 if (!mSimErrorState[index]) {
202 continue;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500203 }
Fabian Kozynskib176f422019-02-05 09:36:59 -0500204 // In the case when no sim cards are detected but a faulty card is inserted
205 // overwrite the text and only show "Invalid card"
206 if (noSims) {
207 return concatenate(carrierTextForSimIOError,
208 getContext().getText(
209 com.android.internal.R.string.emergency_calls_only),
210 mSeparator);
211 } else if (subOrderBySlot[index] != -1) {
212 int subIndex = subOrderBySlot[index];
213 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
214 carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
215 carrierNames[subIndex],
216 mSeparator);
217 } else {
218 // concatenate "Invalid card" when faulty card is inserted in other slot
219 text = concatenate(text, carrierTextForSimIOError, mSeparator);
220 }
221
Fabian Kozynski02941af2019-01-17 17:57:37 -0500222 }
223 return text;
224 }
225
226 /**
227 * Sets the listening status of this controller. If the callback is null, it is set to
228 * not listening
Fabian Kozynski1823f112019-01-18 11:43:29 -0500229 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500230 * @param callback Callback to provide text updates
231 */
232 public void setListening(CarrierTextCallback callback) {
Malcolm Chend3844352019-11-04 16:26:28 -0800233 TelephonyManager telephonyManager = getTelephonyManager();
Fabian Kozynski02941af2019-01-17 17:57:37 -0500234 if (callback != null) {
235 mCarrierTextCallback = callback;
236 if (ConnectivityManager.from(mContext).isNetworkSupported(
237 ConnectivityManager.TYPE_MOBILE)) {
238 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
239 mKeyguardUpdateMonitor.registerCallback(mCallback);
240 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
Malcolm Chen06ec3572019-04-09 15:55:29 -0700241 telephonyManager.listen(mPhoneStateListener,
242 LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500243 } else {
244 // Don't listen and clear out the text when the device isn't a phone.
245 mKeyguardUpdateMonitor = null;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500246 callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500247 }
248 } else {
249 mCarrierTextCallback = null;
250 if (mKeyguardUpdateMonitor != null) {
251 mKeyguardUpdateMonitor.removeCallback(mCallback);
252 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
253 }
Malcolm Chen06ec3572019-04-09 15:55:29 -0700254 telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
255 }
256 }
257
258 /**
Malcolm Chen06ec3572019-04-09 15:55:29 -0700259 * @param subscriptions
260 */
261 private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
262 if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
263 SubscriptionInfo info1 = subscriptions.get(0);
264 SubscriptionInfo info2 = subscriptions.get(1);
265 if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
266 // If both subscriptions are primary, show both.
267 if (!info1.isOpportunistic() && !info2.isOpportunistic()) return;
268
269 // If carrier required, always show signal bar of primary subscription.
270 // Otherwise, show whichever subscription is currently active for Internet.
271 boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig()
272 .getBoolean(CarrierConfigManager
273 .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN);
274 if (alwaysShowPrimary) {
275 subscriptions.remove(info1.isOpportunistic() ? info1 : info2);
276 } else {
277 subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription
278 ? info2 : info1);
279 }
280
281 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500282 }
283 }
284
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700285 /**
286 * updates if opportunistic sub carrier text should be displayed or not
287 *
288 */
289 @VisibleForTesting
Sooraj Sasindranb7d633b2019-05-02 13:47:21 -0700290 public void updateDisplayOpportunisticSubscriptionCarrierText(boolean isEnable) {
291 mDisplayOpportunisticSubscriptionCarrierText = isEnable;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700292 }
293
294 protected List<SubscriptionInfo> getSubscriptionInfo() {
295 List<SubscriptionInfo> subs;
296 if (mDisplayOpportunisticSubscriptionCarrierText) {
297 SubscriptionManager subscriptionManager = ((SubscriptionManager) mContext
298 .getSystemService(
299 Context.TELEPHONY_SUBSCRIPTION_SERVICE));
300 subs = subscriptionManager.getActiveSubscriptionInfoList(false);
301 if (subs == null) {
302 subs = new ArrayList<>();
303 } else {
304 filterMobileSubscriptionInSameGroup(subs);
305 }
306 } else {
307 subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
Fabian Kozynskiccde55d2019-06-26 10:06:09 -0400308 if (subs == null) {
309 subs = new ArrayList<>();
310 } else {
311 filterMobileSubscriptionInSameGroup(subs);
312 }
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700313 }
314 return subs;
315 }
316
Fabian Kozynski02941af2019-01-17 17:57:37 -0500317 protected void updateCarrierText() {
318 boolean allSimsMissing = true;
319 boolean anySimReadyAndInService = false;
320 CharSequence displayText = null;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700321 List<SubscriptionInfo> subs = getSubscriptionInfo();
Malcolm Chen06ec3572019-04-09 15:55:29 -0700322
Fabian Kozynski02941af2019-01-17 17:57:37 -0500323 final int numSubs = subs.size();
Fabian Kozynski1823f112019-01-18 11:43:29 -0500324 final int[] subsIds = new int[numSubs];
Fabian Kozynskib176f422019-02-05 09:36:59 -0500325 // This array will contain in position i, the index of subscription in slot ID i.
326 // -1 if no subscription in that slot
327 final int[] subOrderBySlot = new int[mSimSlotsNumber];
328 for (int i = 0; i < mSimSlotsNumber; i++) {
329 subOrderBySlot[i] = -1;
330 }
331 final CharSequence[] carrierNames = new CharSequence[numSubs];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500332 if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500333
Fabian Kozynski02941af2019-01-17 17:57:37 -0500334 for (int i = 0; i < numSubs; i++) {
335 int subId = subs.get(i).getSubscriptionId();
Fabian Kozynskib176f422019-02-05 09:36:59 -0500336 carrierNames[i] = "";
Fabian Kozynski1823f112019-01-18 11:43:29 -0500337 subsIds[i] = subId;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500338 subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500339 IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
340 CharSequence carrierName = subs.get(i).getCarrierName();
341 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
342 if (DEBUG) {
343 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
344 }
345 if (carrierTextForSimState != null) {
346 allSimsMissing = false;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500347 carrierNames[i] = carrierTextForSimState;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500348 }
349 if (simState == IccCardConstants.State.READY) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000350 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
351 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500352 // hack for WFC (IWLAN) not turning off immediately once
353 // Wi-Fi is disassociated or disabled
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000354 if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
Fabian Kozynski02941af2019-01-17 17:57:37 -0500355 || (mWifiManager.isWifiEnabled()
356 && mWifiManager.getConnectionInfo() != null
357 && mWifiManager.getConnectionInfo().getBSSID() != null)) {
358 if (DEBUG) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000359 Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500360 }
361 anySimReadyAndInService = true;
362 }
363 }
364 }
365 }
366 if (allSimsMissing) {
367 if (numSubs != 0) {
368 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
369 // This depends on mPlmn containing the text "Emergency calls only" when the radio
370 // has some connectivity. Otherwise, it should be null or empty and just show
371 // "No SIM card"
372 // Grab the first subscripton, because they all should contain the emergency text,
373 // described above.
374 displayText = makeCarrierStringOnEmergencyCapable(
375 getMissingSimMessage(), subs.get(0).getCarrierName());
376 } else {
377 // We don't have a SubscriptionInfo to get the emergency calls only from.
378 // Grab it from the old sticky broadcast if possible instead. We can use it
379 // here because no subscriptions are active, so we don't have
380 // to worry about MSIM clashing.
381 CharSequence text =
382 getContext().getText(com.android.internal.R.string.emergency_calls_only);
383 Intent i = getContext().registerReceiver(null,
384 new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
385 if (i != null) {
386 String spn = "";
387 String plmn = "";
388 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
389 spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
390 }
391 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
392 plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
393 }
394 if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
395 if (Objects.equals(plmn, spn)) {
396 text = plmn;
397 } else {
398 text = concatenate(plmn, spn, mSeparator);
399 }
400 }
401 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
402 }
403 }
404
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400405 if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
406
Fabian Kozynskib176f422019-02-05 09:36:59 -0500407 displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
408 allSimsMissing);
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400409
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400410 boolean airplaneMode = false;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500411 // APM (airplane mode) != no carrier state. There are carrier services
412 // (e.g. WFC = Wi-Fi calling) which may operate in APM.
413 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
414 displayText = getAirplaneModeMessage();
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400415 airplaneMode = true;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500416 }
417
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500418 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
419 displayText,
Fabian Kozynskib176f422019-02-05 09:36:59 -0500420 carrierNames,
421 !allSimsMissing,
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400422 subsIds,
423 airplaneMode);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500424 postToCallback(info);
425 }
426
427 @VisibleForTesting
428 protected void postToCallback(CarrierTextCallbackInfo info) {
429 Handler handler = Dependency.get(Dependency.MAIN_HANDLER);
Fabian Kozynski48343c32019-02-07 10:28:50 -0500430 final CarrierTextCallback callback = mCarrierTextCallback;
431 if (callback != null) {
432 handler.post(() -> callback.updateCarrierInfo(info));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500433 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500434 }
435
436 private Context getContext() {
437 return mContext;
438 }
439
440 private String getMissingSimMessage() {
441 return mShowMissingSim && mTelephonyCapable
442 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
443 }
444
445 private String getAirplaneModeMessage() {
446 return mShowAirplaneMode
447 ? getContext().getString(R.string.airplane_mode) : "";
448 }
449
450 /**
451 * Top-level function for creating carrier text. Makes text based on simState, PLMN
452 * and SPN as well as device capabilities, such as being emergency call capable.
453 *
454 * @return Carrier text if not in missing state, null otherwise.
455 */
456 private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
457 CharSequence text) {
458 CharSequence carrierText = null;
459 CarrierTextController.StatusMode status = getStatusForIccState(simState);
460 switch (status) {
461 case Normal:
462 carrierText = text;
463 break;
464
465 case SimNotReady:
466 // Null is reserved for denoting missing, in this case we have nothing to display.
467 carrierText = ""; // nothing to display yet.
468 break;
469
470 case NetworkLocked:
471 carrierText = makeCarrierStringOnEmergencyCapable(
472 mContext.getText(R.string.keyguard_network_locked_message), text);
473 break;
474
475 case SimMissing:
476 carrierText = null;
477 break;
478
479 case SimPermDisabled:
480 carrierText = makeCarrierStringOnEmergencyCapable(
481 getContext().getText(
482 R.string.keyguard_permanent_disabled_sim_message_short),
483 text);
484 break;
485
486 case SimMissingLocked:
487 carrierText = null;
488 break;
489
490 case SimLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400491 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500492 getContext().getText(R.string.keyguard_sim_locked_message),
493 text);
494 break;
495
496 case SimPukLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400497 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500498 getContext().getText(R.string.keyguard_sim_puk_locked_message),
499 text);
500 break;
501 case SimIoError:
502 carrierText = makeCarrierStringOnEmergencyCapable(
503 getContext().getText(R.string.keyguard_sim_error_message_short),
504 text);
505 break;
506 case SimUnknown:
507 carrierText = null;
508 break;
509 }
510
511 return carrierText;
512 }
513
514 /*
515 * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
516 */
517 private CharSequence makeCarrierStringOnEmergencyCapable(
518 CharSequence simMessage, CharSequence emergencyCallMessage) {
519 if (mIsEmergencyCallCapable) {
520 return concatenate(simMessage, emergencyCallMessage, mSeparator);
521 }
522 return simMessage;
523 }
524
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400525 /*
526 * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
527 * DSDS
528 */
529 private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
530 CharSequence carrierName) {
531 final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
532 final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
533 if (simMessageValid && carrierNameValid) {
534 return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
535 carrierName, simMessage);
536 } else if (simMessageValid) {
537 return simMessage;
538 } else if (carrierNameValid) {
539 return carrierName;
540 } else {
541 return "";
542 }
543 }
544
Fabian Kozynski02941af2019-01-17 17:57:37 -0500545 /**
546 * Determine the current status of the lock screen given the SIM state and other stuff.
547 */
548 private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
549 // Since reading the SIM may take a while, we assume it is present until told otherwise.
550 if (simState == null) {
551 return CarrierTextController.StatusMode.Normal;
552 }
553
554 final boolean missingAndNotProvisioned =
555 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
556 && (simState == IccCardConstants.State.ABSENT
557 || simState == IccCardConstants.State.PERM_DISABLED);
558
559 // Assume we're NETWORK_LOCKED if not provisioned
560 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
561 switch (simState) {
562 case ABSENT:
563 return CarrierTextController.StatusMode.SimMissing;
564 case NETWORK_LOCKED:
565 return CarrierTextController.StatusMode.SimMissingLocked;
566 case NOT_READY:
567 return CarrierTextController.StatusMode.SimNotReady;
568 case PIN_REQUIRED:
569 return CarrierTextController.StatusMode.SimLocked;
570 case PUK_REQUIRED:
571 return CarrierTextController.StatusMode.SimPukLocked;
572 case READY:
573 return CarrierTextController.StatusMode.Normal;
574 case PERM_DISABLED:
575 return CarrierTextController.StatusMode.SimPermDisabled;
576 case UNKNOWN:
577 return CarrierTextController.StatusMode.SimUnknown;
578 case CARD_IO_ERROR:
579 return CarrierTextController.StatusMode.SimIoError;
580 }
581 return CarrierTextController.StatusMode.SimUnknown;
582 }
583
584 private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
585 CharSequence separator) {
586 final boolean plmnValid = !TextUtils.isEmpty(plmn);
587 final boolean spnValid = !TextUtils.isEmpty(spn);
588 if (plmnValid && spnValid) {
589 return new StringBuilder().append(plmn).append(separator).append(spn).toString();
590 } else if (plmnValid) {
591 return plmn;
592 } else if (spnValid) {
593 return spn;
594 } else {
595 return "";
596 }
597 }
598
Fabian Kozynski00d02f12019-04-15 09:48:30 -0400599 /**
600 * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
601 * separator added so there are no extra separators that are not needed.
602 */
603 private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
604 int length = sequences.length;
605 if (length == 0) return "";
606 StringBuilder sb = new StringBuilder();
607 for (int i = 0; i < length; i++) {
608 if (!TextUtils.isEmpty(sequences[i])) {
609 if (!TextUtils.isEmpty(sb)) {
610 sb.append(separator);
611 }
612 sb.append(sequences[i]);
613 }
614 }
615 return sb.toString();
616 }
617
Fabian Kozynski02941af2019-01-17 17:57:37 -0500618 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
619 if (!TextUtils.isEmpty(string)) {
620 list.add(string);
621 }
622 return list;
623 }
624
625 private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
626 String plmn, String spn) {
627 int carrierHelpTextId = 0;
628 CarrierTextController.StatusMode status = getStatusForIccState(simState);
629 switch (status) {
630 case NetworkLocked:
631 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
632 break;
633
634 case SimMissing:
635 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
636 break;
637
638 case SimPermDisabled:
639 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
640 break;
641
642 case SimMissingLocked:
643 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
644 break;
645
646 case Normal:
647 case SimLocked:
648 case SimPukLocked:
649 break;
650 }
651
652 return mContext.getText(carrierHelpTextId);
653 }
654
655 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500656 * Data structure for passing information to CarrierTextController subscribers
657 */
658 public static final class CarrierTextCallbackInfo {
659 public final CharSequence carrierText;
660 public final CharSequence[] listOfCarriers;
661 public final boolean anySimReady;
662 public final int[] subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400663 public boolean airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500664
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500665 @VisibleForTesting
666 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
Fabian Kozynski1823f112019-01-18 11:43:29 -0500667 boolean anySimReady, int[] subscriptionIds) {
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400668 this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
669 }
670
671 @VisibleForTesting
672 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
673 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
Fabian Kozynski1823f112019-01-18 11:43:29 -0500674 this.carrierText = carrierText;
675 this.listOfCarriers = listOfCarriers;
676 this.anySimReady = anySimReady;
677 this.subscriptionIds = subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400678 this.airplaneMode = airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500679 }
680 }
681
682 /**
Fabian Kozynski02941af2019-01-17 17:57:37 -0500683 * Callback to communicate to Views
684 */
685 public interface CarrierTextCallback {
686 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500687 * Provides updated carrier information.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500688 */
Fabian Kozynski1823f112019-01-18 11:43:29 -0500689 default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
Fabian Kozynski02941af2019-01-17 17:57:37 -0500690
691 /**
692 * Notifies the View that the device is going to sleep
693 */
694 default void startedGoingToSleep() {};
695
696 /**
697 * Notifies the View that the device finished waking up
698 */
699 default void finishedWakingUp() {};
700 }
701}