blob: 3f3d8a5bfa45ef484e42eb629a42fa52bb4ac3ba [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;
Sunny Goyal87fccf02019-08-13 17:39:10 -070047import com.android.systemui.R;
Fabian Kozynski02941af2019-01-17 17:57:37 -050048import com.android.systemui.keyguard.WakefulnessLifecycle;
49
Malcolm Chen06ec3572019-04-09 15:55:29 -070050import java.util.ArrayList;
Fabian Kozynski02941af2019-01-17 17:57:37 -050051import java.util.List;
52import java.util.Objects;
53
54/**
55 * Controller that generates text including the carrier names and/or the status of all the SIM
56 * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
57 * separated by a given separator {@link CharSequence}.
58 */
59public class CarrierTextController {
60 private static final boolean DEBUG = KeyguardConstants.DEBUG;
61 private static final String TAG = "CarrierTextController";
62
63 private final boolean mIsEmergencyCallCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050064 private boolean mTelephonyCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050065 private boolean mShowMissingSim;
Fabian Kozynski02941af2019-01-17 17:57:37 -050066 private boolean mShowAirplaneMode;
Fabian Kozynskib176f422019-02-05 09:36:59 -050067 @VisibleForTesting
68 protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
Fabian Kozynski02941af2019-01-17 17:57:37 -050069 private WifiManager mWifiManager;
Fabian Kozynskib176f422019-02-05 09:36:59 -050070 private boolean[] mSimErrorState;
71 private final int mSimSlotsNumber;
Fabian Kozynski02941af2019-01-17 17:57:37 -050072 private CarrierTextCallback mCarrierTextCallback;
73 private Context mContext;
74 private CharSequence mSeparator;
75 private WakefulnessLifecycle mWakefulnessLifecycle;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -070076 @VisibleForTesting
77 protected boolean mDisplayOpportunisticSubscriptionCarrierText;
Fabian Kozynski02941af2019-01-17 17:57:37 -050078 private final WakefulnessLifecycle.Observer mWakefulnessObserver =
79 new WakefulnessLifecycle.Observer() {
80 @Override
81 public void onFinishedWakingUp() {
82 mCarrierTextCallback.finishedWakingUp();
83 }
84
85 @Override
86 public void onStartedGoingToSleep() {
87 mCarrierTextCallback.startedGoingToSleep();
88 }
89 };
90
Fabian Kozynskib176f422019-02-05 09:36:59 -050091 @VisibleForTesting
92 protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
Fabian Kozynski02941af2019-01-17 17:57:37 -050093 @Override
94 public void onRefreshCarrierInfo() {
95 if (DEBUG) {
96 Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
97 + Boolean.toString(mTelephonyCapable));
98 }
99 updateCarrierText();
100 }
101
102 @Override
103 public void onTelephonyCapable(boolean capable) {
104 if (DEBUG) {
105 Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
106 + Boolean.toString(capable));
107 }
108 mTelephonyCapable = capable;
109 updateCarrierText();
110 }
111
112 public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500113 if (slotId < 0 || slotId >= mSimSlotsNumber) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500114 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
115 + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
116 return;
117 }
118
119 if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
120 if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
121 mSimErrorState[slotId] = true;
122 updateCarrierText();
123 } else if (mSimErrorState[slotId]) {
124 mSimErrorState[slotId] = false;
125 updateCarrierText();
126 }
127 }
128 };
129
Malcolm Chen06ec3572019-04-09 15:55:29 -0700130 private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
131 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
132 @Override
133 public void onActiveDataSubscriptionIdChanged(int subId) {
134 mActiveMobileDataSubscription = subId;
135 if (mKeyguardUpdateMonitor != null) {
136 updateCarrierText();
137 }
138 }
139 };
140
Fabian Kozynski02941af2019-01-17 17:57:37 -0500141 /**
142 * The status of this lock screen. Primarily used for widgets on LockScreen.
143 */
144 private enum StatusMode {
145 Normal, // Normal case (sim card present, it's not locked)
146 NetworkLocked, // SIM card is 'network locked'.
147 SimMissing, // SIM card is missing.
148 SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
149 SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
150 SimLocked, // SIM card is currently locked
151 SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
152 SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
153 SimIoError, // SIM card is faulty
154 SimUnknown // SIM card is unknown
155 }
156
157 /**
158 * Controller that provides updates on text with carriers names or SIM status.
159 * Used by {@link CarrierText}.
Fabian Kozynski1823f112019-01-18 11:43:29 -0500160 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500161 * @param separator Separator between different parts of the text
Fabian Kozynski02941af2019-01-17 17:57:37 -0500162 */
163 public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
164 boolean showMissingSim) {
165 mContext = context;
166 mIsEmergencyCallCapable = context.getResources().getBoolean(
167 com.android.internal.R.bool.config_voice_capable);
168
169 mShowAirplaneMode = showAirplaneMode;
170 mShowMissingSim = showMissingSim;
171
172 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
173 mSeparator = separator;
174 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500175 mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
176 Context.TELEPHONY_SERVICE)).getPhoneCount();
177 mSimErrorState = new boolean[mSimSlotsNumber];
Sooraj Sasindranb7d633b2019-05-02 13:47:21 -0700178 updateDisplayOpportunisticSubscriptionCarrierText(SystemProperties.getBoolean(
179 TelephonyProperties.DISPLAY_OPPORTUNISTIC_SUBSCRIPTION_CARRIER_TEXT_PROPERTY_NAME,
180 false));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500181 }
182
183 /**
184 * Checks if there are faulty cards. Adds the text depending on the slot of the card
185 *
186 * @param text: current carrier text based on the sim state
Fabian Kozynskib176f422019-02-05 09:36:59 -0500187 * @param carrierNames names order by subscription order
188 * @param subOrderBySlot array containing the sub index for each slot ID
Fabian Kozynski02941af2019-01-17 17:57:37 -0500189 * @param noSims: whether a valid sim card is inserted
190 * @return text
191 */
Fabian Kozynskib176f422019-02-05 09:36:59 -0500192 private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
193 CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500194 final CharSequence carrier = "";
195 CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
196 IccCardConstants.State.CARD_IO_ERROR, carrier);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500197 // mSimErrorState has the state of each sim indexed by slotID.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500198 for (int index = 0; index < mSimErrorState.length; index++) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500199 if (!mSimErrorState[index]) {
200 continue;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500201 }
Fabian Kozynskib176f422019-02-05 09:36:59 -0500202 // In the case when no sim cards are detected but a faulty card is inserted
203 // overwrite the text and only show "Invalid card"
204 if (noSims) {
205 return concatenate(carrierTextForSimIOError,
206 getContext().getText(
207 com.android.internal.R.string.emergency_calls_only),
208 mSeparator);
209 } else if (subOrderBySlot[index] != -1) {
210 int subIndex = subOrderBySlot[index];
211 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
212 carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
213 carrierNames[subIndex],
214 mSeparator);
215 } else {
216 // concatenate "Invalid card" when faulty card is inserted in other slot
217 text = concatenate(text, carrierTextForSimIOError, mSeparator);
218 }
219
Fabian Kozynski02941af2019-01-17 17:57:37 -0500220 }
221 return text;
222 }
223
224 /**
225 * Sets the listening status of this controller. If the callback is null, it is set to
226 * not listening
Fabian Kozynski1823f112019-01-18 11:43:29 -0500227 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500228 * @param callback Callback to provide text updates
229 */
230 public void setListening(CarrierTextCallback callback) {
Malcolm Chen06ec3572019-04-09 15:55:29 -0700231 TelephonyManager telephonyManager = ((TelephonyManager) mContext
232 .getSystemService(Context.TELEPHONY_SERVICE));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500233 if (callback != null) {
234 mCarrierTextCallback = callback;
235 if (ConnectivityManager.from(mContext).isNetworkSupported(
236 ConnectivityManager.TYPE_MOBILE)) {
237 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
238 mKeyguardUpdateMonitor.registerCallback(mCallback);
239 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
Malcolm Chen06ec3572019-04-09 15:55:29 -0700240 telephonyManager.listen(mPhoneStateListener,
241 LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500242 } else {
243 // Don't listen and clear out the text when the device isn't a phone.
244 mKeyguardUpdateMonitor = null;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500245 callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500246 }
247 } else {
248 mCarrierTextCallback = null;
249 if (mKeyguardUpdateMonitor != null) {
250 mKeyguardUpdateMonitor.removeCallback(mCallback);
251 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
252 }
Malcolm Chen06ec3572019-04-09 15:55:29 -0700253 telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
254 }
255 }
256
257 /**
Malcolm Chen06ec3572019-04-09 15:55:29 -0700258 * @param subscriptions
259 */
260 private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
261 if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
262 SubscriptionInfo info1 = subscriptions.get(0);
263 SubscriptionInfo info2 = subscriptions.get(1);
264 if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
265 // If both subscriptions are primary, show both.
266 if (!info1.isOpportunistic() && !info2.isOpportunistic()) return;
267
268 // If carrier required, always show signal bar of primary subscription.
269 // Otherwise, show whichever subscription is currently active for Internet.
270 boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig()
271 .getBoolean(CarrierConfigManager
272 .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN);
273 if (alwaysShowPrimary) {
274 subscriptions.remove(info1.isOpportunistic() ? info1 : info2);
275 } else {
276 subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription
277 ? info2 : info1);
278 }
279
280 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500281 }
282 }
283
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700284 /**
285 * updates if opportunistic sub carrier text should be displayed or not
286 *
287 */
288 @VisibleForTesting
Sooraj Sasindranb7d633b2019-05-02 13:47:21 -0700289 public void updateDisplayOpportunisticSubscriptionCarrierText(boolean isEnable) {
290 mDisplayOpportunisticSubscriptionCarrierText = isEnable;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700291 }
292
293 protected List<SubscriptionInfo> getSubscriptionInfo() {
294 List<SubscriptionInfo> subs;
295 if (mDisplayOpportunisticSubscriptionCarrierText) {
296 SubscriptionManager subscriptionManager = ((SubscriptionManager) mContext
297 .getSystemService(
298 Context.TELEPHONY_SUBSCRIPTION_SERVICE));
299 subs = subscriptionManager.getActiveSubscriptionInfoList(false);
300 if (subs == null) {
301 subs = new ArrayList<>();
302 } else {
303 filterMobileSubscriptionInSameGroup(subs);
304 }
305 } else {
306 subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
Fabian Kozynskiccde55d2019-06-26 10:06:09 -0400307 if (subs == null) {
308 subs = new ArrayList<>();
309 } else {
310 filterMobileSubscriptionInSameGroup(subs);
311 }
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700312 }
313 return subs;
314 }
315
Fabian Kozynski02941af2019-01-17 17:57:37 -0500316 protected void updateCarrierText() {
317 boolean allSimsMissing = true;
318 boolean anySimReadyAndInService = false;
319 CharSequence displayText = null;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700320 List<SubscriptionInfo> subs = getSubscriptionInfo();
Malcolm Chen06ec3572019-04-09 15:55:29 -0700321
Fabian Kozynski02941af2019-01-17 17:57:37 -0500322 final int numSubs = subs.size();
Fabian Kozynski1823f112019-01-18 11:43:29 -0500323 final int[] subsIds = new int[numSubs];
Fabian Kozynskib176f422019-02-05 09:36:59 -0500324 // This array will contain in position i, the index of subscription in slot ID i.
325 // -1 if no subscription in that slot
326 final int[] subOrderBySlot = new int[mSimSlotsNumber];
327 for (int i = 0; i < mSimSlotsNumber; i++) {
328 subOrderBySlot[i] = -1;
329 }
330 final CharSequence[] carrierNames = new CharSequence[numSubs];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500331 if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500332
Fabian Kozynski02941af2019-01-17 17:57:37 -0500333 for (int i = 0; i < numSubs; i++) {
334 int subId = subs.get(i).getSubscriptionId();
Fabian Kozynskib176f422019-02-05 09:36:59 -0500335 carrierNames[i] = "";
Fabian Kozynski1823f112019-01-18 11:43:29 -0500336 subsIds[i] = subId;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500337 subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500338 IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
339 CharSequence carrierName = subs.get(i).getCarrierName();
340 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
341 if (DEBUG) {
342 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
343 }
344 if (carrierTextForSimState != null) {
345 allSimsMissing = false;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500346 carrierNames[i] = carrierTextForSimState;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500347 }
348 if (simState == IccCardConstants.State.READY) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000349 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
350 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500351 // hack for WFC (IWLAN) not turning off immediately once
352 // Wi-Fi is disassociated or disabled
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000353 if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
Fabian Kozynski02941af2019-01-17 17:57:37 -0500354 || (mWifiManager.isWifiEnabled()
355 && mWifiManager.getConnectionInfo() != null
356 && mWifiManager.getConnectionInfo().getBSSID() != null)) {
357 if (DEBUG) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000358 Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500359 }
360 anySimReadyAndInService = true;
361 }
362 }
363 }
364 }
Fabian Kozynski4e63d9e2019-08-29 09:47:07 -0400365 // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
366 // This condition will also be true always when numSubs == 0
367 if (allSimsMissing && !anySimReadyAndInService) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500368 if (numSubs != 0) {
369 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
370 // This depends on mPlmn containing the text "Emergency calls only" when the radio
371 // has some connectivity. Otherwise, it should be null or empty and just show
372 // "No SIM card"
373 // Grab the first subscripton, because they all should contain the emergency text,
374 // described above.
375 displayText = makeCarrierStringOnEmergencyCapable(
376 getMissingSimMessage(), subs.get(0).getCarrierName());
377 } else {
378 // We don't have a SubscriptionInfo to get the emergency calls only from.
379 // Grab it from the old sticky broadcast if possible instead. We can use it
380 // here because no subscriptions are active, so we don't have
381 // to worry about MSIM clashing.
382 CharSequence text =
383 getContext().getText(com.android.internal.R.string.emergency_calls_only);
384 Intent i = getContext().registerReceiver(null,
385 new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
386 if (i != null) {
387 String spn = "";
388 String plmn = "";
389 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
390 spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
391 }
392 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
393 plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
394 }
395 if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
396 if (Objects.equals(plmn, spn)) {
397 text = plmn;
398 } else {
399 text = concatenate(plmn, spn, mSeparator);
400 }
401 }
402 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
403 }
404 }
405
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400406 if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
407
Fabian Kozynskib176f422019-02-05 09:36:59 -0500408 displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
409 allSimsMissing);
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400410
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400411 boolean airplaneMode = false;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500412 // APM (airplane mode) != no carrier state. There are carrier services
413 // (e.g. WFC = Wi-Fi calling) which may operate in APM.
414 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
415 displayText = getAirplaneModeMessage();
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400416 airplaneMode = true;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500417 }
418
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500419 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
420 displayText,
Fabian Kozynskib176f422019-02-05 09:36:59 -0500421 carrierNames,
422 !allSimsMissing,
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400423 subsIds,
424 airplaneMode);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500425 postToCallback(info);
426 }
427
428 @VisibleForTesting
429 protected void postToCallback(CarrierTextCallbackInfo info) {
430 Handler handler = Dependency.get(Dependency.MAIN_HANDLER);
Fabian Kozynski48343c32019-02-07 10:28:50 -0500431 final CarrierTextCallback callback = mCarrierTextCallback;
432 if (callback != null) {
433 handler.post(() -> callback.updateCarrierInfo(info));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500434 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500435 }
436
437 private Context getContext() {
438 return mContext;
439 }
440
441 private String getMissingSimMessage() {
442 return mShowMissingSim && mTelephonyCapable
443 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
444 }
445
446 private String getAirplaneModeMessage() {
447 return mShowAirplaneMode
448 ? getContext().getString(R.string.airplane_mode) : "";
449 }
450
451 /**
452 * Top-level function for creating carrier text. Makes text based on simState, PLMN
453 * and SPN as well as device capabilities, such as being emergency call capable.
454 *
455 * @return Carrier text if not in missing state, null otherwise.
456 */
457 private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
458 CharSequence text) {
459 CharSequence carrierText = null;
460 CarrierTextController.StatusMode status = getStatusForIccState(simState);
461 switch (status) {
462 case Normal:
463 carrierText = text;
464 break;
465
466 case SimNotReady:
467 // Null is reserved for denoting missing, in this case we have nothing to display.
468 carrierText = ""; // nothing to display yet.
469 break;
470
471 case NetworkLocked:
472 carrierText = makeCarrierStringOnEmergencyCapable(
473 mContext.getText(R.string.keyguard_network_locked_message), text);
474 break;
475
476 case SimMissing:
477 carrierText = null;
478 break;
479
480 case SimPermDisabled:
481 carrierText = makeCarrierStringOnEmergencyCapable(
482 getContext().getText(
483 R.string.keyguard_permanent_disabled_sim_message_short),
484 text);
485 break;
486
487 case SimMissingLocked:
488 carrierText = null;
489 break;
490
491 case SimLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400492 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500493 getContext().getText(R.string.keyguard_sim_locked_message),
494 text);
495 break;
496
497 case SimPukLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400498 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500499 getContext().getText(R.string.keyguard_sim_puk_locked_message),
500 text);
501 break;
502 case SimIoError:
503 carrierText = makeCarrierStringOnEmergencyCapable(
504 getContext().getText(R.string.keyguard_sim_error_message_short),
505 text);
506 break;
507 case SimUnknown:
508 carrierText = null;
509 break;
510 }
511
512 return carrierText;
513 }
514
515 /*
516 * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
517 */
518 private CharSequence makeCarrierStringOnEmergencyCapable(
519 CharSequence simMessage, CharSequence emergencyCallMessage) {
520 if (mIsEmergencyCallCapable) {
521 return concatenate(simMessage, emergencyCallMessage, mSeparator);
522 }
523 return simMessage;
524 }
525
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400526 /*
527 * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
528 * DSDS
529 */
530 private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
531 CharSequence carrierName) {
532 final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
533 final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
534 if (simMessageValid && carrierNameValid) {
535 return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
536 carrierName, simMessage);
537 } else if (simMessageValid) {
538 return simMessage;
539 } else if (carrierNameValid) {
540 return carrierName;
541 } else {
542 return "";
543 }
544 }
545
Fabian Kozynski02941af2019-01-17 17:57:37 -0500546 /**
547 * Determine the current status of the lock screen given the SIM state and other stuff.
548 */
549 private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
550 // Since reading the SIM may take a while, we assume it is present until told otherwise.
551 if (simState == null) {
552 return CarrierTextController.StatusMode.Normal;
553 }
554
555 final boolean missingAndNotProvisioned =
556 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
557 && (simState == IccCardConstants.State.ABSENT
558 || simState == IccCardConstants.State.PERM_DISABLED);
559
560 // Assume we're NETWORK_LOCKED if not provisioned
561 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
562 switch (simState) {
563 case ABSENT:
564 return CarrierTextController.StatusMode.SimMissing;
565 case NETWORK_LOCKED:
566 return CarrierTextController.StatusMode.SimMissingLocked;
567 case NOT_READY:
568 return CarrierTextController.StatusMode.SimNotReady;
569 case PIN_REQUIRED:
570 return CarrierTextController.StatusMode.SimLocked;
571 case PUK_REQUIRED:
572 return CarrierTextController.StatusMode.SimPukLocked;
573 case READY:
574 return CarrierTextController.StatusMode.Normal;
575 case PERM_DISABLED:
576 return CarrierTextController.StatusMode.SimPermDisabled;
577 case UNKNOWN:
578 return CarrierTextController.StatusMode.SimUnknown;
579 case CARD_IO_ERROR:
580 return CarrierTextController.StatusMode.SimIoError;
581 }
582 return CarrierTextController.StatusMode.SimUnknown;
583 }
584
585 private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
586 CharSequence separator) {
587 final boolean plmnValid = !TextUtils.isEmpty(plmn);
588 final boolean spnValid = !TextUtils.isEmpty(spn);
589 if (plmnValid && spnValid) {
590 return new StringBuilder().append(plmn).append(separator).append(spn).toString();
591 } else if (plmnValid) {
592 return plmn;
593 } else if (spnValid) {
594 return spn;
595 } else {
596 return "";
597 }
598 }
599
Fabian Kozynski00d02f12019-04-15 09:48:30 -0400600 /**
601 * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
602 * separator added so there are no extra separators that are not needed.
603 */
604 private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
605 int length = sequences.length;
606 if (length == 0) return "";
607 StringBuilder sb = new StringBuilder();
608 for (int i = 0; i < length; i++) {
609 if (!TextUtils.isEmpty(sequences[i])) {
610 if (!TextUtils.isEmpty(sb)) {
611 sb.append(separator);
612 }
613 sb.append(sequences[i]);
614 }
615 }
616 return sb.toString();
617 }
618
Fabian Kozynski02941af2019-01-17 17:57:37 -0500619 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
620 if (!TextUtils.isEmpty(string)) {
621 list.add(string);
622 }
623 return list;
624 }
625
626 private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
627 String plmn, String spn) {
628 int carrierHelpTextId = 0;
629 CarrierTextController.StatusMode status = getStatusForIccState(simState);
630 switch (status) {
631 case NetworkLocked:
632 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
633 break;
634
635 case SimMissing:
636 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
637 break;
638
639 case SimPermDisabled:
640 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
641 break;
642
643 case SimMissingLocked:
644 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
645 break;
646
647 case Normal:
648 case SimLocked:
649 case SimPukLocked:
650 break;
651 }
652
653 return mContext.getText(carrierHelpTextId);
654 }
655
656 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500657 * Data structure for passing information to CarrierTextController subscribers
658 */
659 public static final class CarrierTextCallbackInfo {
660 public final CharSequence carrierText;
661 public final CharSequence[] listOfCarriers;
662 public final boolean anySimReady;
663 public final int[] subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400664 public boolean airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500665
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500666 @VisibleForTesting
667 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
Fabian Kozynski1823f112019-01-18 11:43:29 -0500668 boolean anySimReady, int[] subscriptionIds) {
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400669 this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
670 }
671
672 @VisibleForTesting
673 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
674 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
Fabian Kozynski1823f112019-01-18 11:43:29 -0500675 this.carrierText = carrierText;
676 this.listOfCarriers = listOfCarriers;
677 this.anySimReady = anySimReady;
678 this.subscriptionIds = subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400679 this.airplaneMode = airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500680 }
681 }
682
683 /**
Fabian Kozynski02941af2019-01-17 17:57:37 -0500684 * Callback to communicate to Views
685 */
686 public interface CarrierTextCallback {
687 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500688 * Provides updated carrier information.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500689 */
Fabian Kozynski1823f112019-01-18 11:43:29 -0500690 default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
Fabian Kozynski02941af2019-01-17 17:57:37 -0500691
692 /**
693 * Notifies the View that the device is going to sleep
694 */
695 default void startedGoingToSleep() {};
696
697 /**
698 * Notifies the View that the device finished waking up
699 */
700 default void finishedWakingUp() {};
701 }
702}