blob: 0efc5bf411f3b7a9ad67f28e651133a3d517c075 [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);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500174 mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
Malcolm Chen387dc0e2019-10-08 18:11:22 -0700175 Context.TELEPHONY_SERVICE)).getSupportedModemCount();
Fabian Kozynskib176f422019-02-05 09:36:59 -0500176 mSimErrorState = new boolean[mSimSlotsNumber];
Sooraj Sasindranb7d633b2019-05-02 13:47:21 -0700177 updateDisplayOpportunisticSubscriptionCarrierText(SystemProperties.getBoolean(
178 TelephonyProperties.DISPLAY_OPPORTUNISTIC_SUBSCRIPTION_CARRIER_TEXT_PROPERTY_NAME,
179 false));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500180 }
181
182 /**
183 * Checks if there are faulty cards. Adds the text depending on the slot of the card
184 *
185 * @param text: current carrier text based on the sim state
Fabian Kozynskib176f422019-02-05 09:36:59 -0500186 * @param carrierNames names order by subscription order
187 * @param subOrderBySlot array containing the sub index for each slot ID
Fabian Kozynski02941af2019-01-17 17:57:37 -0500188 * @param noSims: whether a valid sim card is inserted
189 * @return text
190 */
Fabian Kozynskib176f422019-02-05 09:36:59 -0500191 private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
192 CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500193 final CharSequence carrier = "";
194 CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
195 IccCardConstants.State.CARD_IO_ERROR, carrier);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500196 // mSimErrorState has the state of each sim indexed by slotID.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500197 for (int index = 0; index < mSimErrorState.length; index++) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500198 if (!mSimErrorState[index]) {
199 continue;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500200 }
Fabian Kozynskib176f422019-02-05 09:36:59 -0500201 // In the case when no sim cards are detected but a faulty card is inserted
202 // overwrite the text and only show "Invalid card"
203 if (noSims) {
204 return concatenate(carrierTextForSimIOError,
205 getContext().getText(
206 com.android.internal.R.string.emergency_calls_only),
207 mSeparator);
208 } else if (subOrderBySlot[index] != -1) {
209 int subIndex = subOrderBySlot[index];
210 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
211 carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
212 carrierNames[subIndex],
213 mSeparator);
214 } else {
215 // concatenate "Invalid card" when faulty card is inserted in other slot
216 text = concatenate(text, carrierTextForSimIOError, mSeparator);
217 }
218
Fabian Kozynski02941af2019-01-17 17:57:37 -0500219 }
220 return text;
221 }
222
223 /**
224 * Sets the listening status of this controller. If the callback is null, it is set to
225 * not listening
Fabian Kozynski1823f112019-01-18 11:43:29 -0500226 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500227 * @param callback Callback to provide text updates
228 */
229 public void setListening(CarrierTextCallback callback) {
Malcolm Chen06ec3572019-04-09 15:55:29 -0700230 TelephonyManager telephonyManager = ((TelephonyManager) mContext
231 .getSystemService(Context.TELEPHONY_SERVICE));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500232 if (callback != null) {
233 mCarrierTextCallback = callback;
234 if (ConnectivityManager.from(mContext).isNetworkSupported(
235 ConnectivityManager.TYPE_MOBILE)) {
236 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
237 mKeyguardUpdateMonitor.registerCallback(mCallback);
238 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
Malcolm Chen06ec3572019-04-09 15:55:29 -0700239 telephonyManager.listen(mPhoneStateListener,
240 LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500241 } else {
242 // Don't listen and clear out the text when the device isn't a phone.
243 mKeyguardUpdateMonitor = null;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500244 callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500245 }
246 } else {
247 mCarrierTextCallback = null;
248 if (mKeyguardUpdateMonitor != null) {
249 mKeyguardUpdateMonitor.removeCallback(mCallback);
250 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
251 }
Malcolm Chen06ec3572019-04-09 15:55:29 -0700252 telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
253 }
254 }
255
256 /**
Malcolm Chen06ec3572019-04-09 15:55:29 -0700257 * @param subscriptions
258 */
259 private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
260 if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
261 SubscriptionInfo info1 = subscriptions.get(0);
262 SubscriptionInfo info2 = subscriptions.get(1);
263 if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
264 // If both subscriptions are primary, show both.
265 if (!info1.isOpportunistic() && !info2.isOpportunistic()) return;
266
267 // If carrier required, always show signal bar of primary subscription.
268 // Otherwise, show whichever subscription is currently active for Internet.
269 boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig()
270 .getBoolean(CarrierConfigManager
271 .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN);
272 if (alwaysShowPrimary) {
273 subscriptions.remove(info1.isOpportunistic() ? info1 : info2);
274 } else {
275 subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription
276 ? info2 : info1);
277 }
278
279 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500280 }
281 }
282
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700283 /**
284 * updates if opportunistic sub carrier text should be displayed or not
285 *
286 */
287 @VisibleForTesting
Sooraj Sasindranb7d633b2019-05-02 13:47:21 -0700288 public void updateDisplayOpportunisticSubscriptionCarrierText(boolean isEnable) {
289 mDisplayOpportunisticSubscriptionCarrierText = isEnable;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700290 }
291
292 protected List<SubscriptionInfo> getSubscriptionInfo() {
293 List<SubscriptionInfo> subs;
294 if (mDisplayOpportunisticSubscriptionCarrierText) {
295 SubscriptionManager subscriptionManager = ((SubscriptionManager) mContext
296 .getSystemService(
297 Context.TELEPHONY_SUBSCRIPTION_SERVICE));
298 subs = subscriptionManager.getActiveSubscriptionInfoList(false);
299 if (subs == null) {
300 subs = new ArrayList<>();
301 } else {
302 filterMobileSubscriptionInSameGroup(subs);
303 }
304 } else {
305 subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
Fabian Kozynskiccde55d2019-06-26 10:06:09 -0400306 if (subs == null) {
307 subs = new ArrayList<>();
308 } else {
309 filterMobileSubscriptionInSameGroup(subs);
310 }
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700311 }
312 return subs;
313 }
314
Fabian Kozynski02941af2019-01-17 17:57:37 -0500315 protected void updateCarrierText() {
316 boolean allSimsMissing = true;
317 boolean anySimReadyAndInService = false;
318 CharSequence displayText = null;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700319 List<SubscriptionInfo> subs = getSubscriptionInfo();
Malcolm Chen06ec3572019-04-09 15:55:29 -0700320
Fabian Kozynski02941af2019-01-17 17:57:37 -0500321 final int numSubs = subs.size();
Fabian Kozynski1823f112019-01-18 11:43:29 -0500322 final int[] subsIds = new int[numSubs];
Fabian Kozynskib176f422019-02-05 09:36:59 -0500323 // This array will contain in position i, the index of subscription in slot ID i.
324 // -1 if no subscription in that slot
325 final int[] subOrderBySlot = new int[mSimSlotsNumber];
326 for (int i = 0; i < mSimSlotsNumber; i++) {
327 subOrderBySlot[i] = -1;
328 }
329 final CharSequence[] carrierNames = new CharSequence[numSubs];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500330 if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500331
Fabian Kozynski02941af2019-01-17 17:57:37 -0500332 for (int i = 0; i < numSubs; i++) {
333 int subId = subs.get(i).getSubscriptionId();
Fabian Kozynskib176f422019-02-05 09:36:59 -0500334 carrierNames[i] = "";
Fabian Kozynski1823f112019-01-18 11:43:29 -0500335 subsIds[i] = subId;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500336 subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500337 IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
338 CharSequence carrierName = subs.get(i).getCarrierName();
339 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
340 if (DEBUG) {
341 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
342 }
343 if (carrierTextForSimState != null) {
344 allSimsMissing = false;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500345 carrierNames[i] = carrierTextForSimState;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500346 }
347 if (simState == IccCardConstants.State.READY) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000348 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
349 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500350 // hack for WFC (IWLAN) not turning off immediately once
351 // Wi-Fi is disassociated or disabled
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000352 if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
Fabian Kozynski02941af2019-01-17 17:57:37 -0500353 || (mWifiManager.isWifiEnabled()
354 && mWifiManager.getConnectionInfo() != null
355 && mWifiManager.getConnectionInfo().getBSSID() != null)) {
356 if (DEBUG) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000357 Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500358 }
359 anySimReadyAndInService = true;
360 }
361 }
362 }
363 }
364 if (allSimsMissing) {
365 if (numSubs != 0) {
366 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
367 // This depends on mPlmn containing the text "Emergency calls only" when the radio
368 // has some connectivity. Otherwise, it should be null or empty and just show
369 // "No SIM card"
370 // Grab the first subscripton, because they all should contain the emergency text,
371 // described above.
372 displayText = makeCarrierStringOnEmergencyCapable(
373 getMissingSimMessage(), subs.get(0).getCarrierName());
374 } else {
375 // We don't have a SubscriptionInfo to get the emergency calls only from.
376 // Grab it from the old sticky broadcast if possible instead. We can use it
377 // here because no subscriptions are active, so we don't have
378 // to worry about MSIM clashing.
379 CharSequence text =
380 getContext().getText(com.android.internal.R.string.emergency_calls_only);
381 Intent i = getContext().registerReceiver(null,
382 new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
383 if (i != null) {
384 String spn = "";
385 String plmn = "";
386 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
387 spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
388 }
389 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
390 plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
391 }
392 if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
393 if (Objects.equals(plmn, spn)) {
394 text = plmn;
395 } else {
396 text = concatenate(plmn, spn, mSeparator);
397 }
398 }
399 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
400 }
401 }
402
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400403 if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
404
Fabian Kozynskib176f422019-02-05 09:36:59 -0500405 displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
406 allSimsMissing);
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400407
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400408 boolean airplaneMode = false;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500409 // APM (airplane mode) != no carrier state. There are carrier services
410 // (e.g. WFC = Wi-Fi calling) which may operate in APM.
411 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
412 displayText = getAirplaneModeMessage();
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400413 airplaneMode = true;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500414 }
415
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500416 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
417 displayText,
Fabian Kozynskib176f422019-02-05 09:36:59 -0500418 carrierNames,
419 !allSimsMissing,
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400420 subsIds,
421 airplaneMode);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500422 postToCallback(info);
423 }
424
425 @VisibleForTesting
426 protected void postToCallback(CarrierTextCallbackInfo info) {
427 Handler handler = Dependency.get(Dependency.MAIN_HANDLER);
Fabian Kozynski48343c32019-02-07 10:28:50 -0500428 final CarrierTextCallback callback = mCarrierTextCallback;
429 if (callback != null) {
430 handler.post(() -> callback.updateCarrierInfo(info));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500431 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500432 }
433
434 private Context getContext() {
435 return mContext;
436 }
437
438 private String getMissingSimMessage() {
439 return mShowMissingSim && mTelephonyCapable
440 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
441 }
442
443 private String getAirplaneModeMessage() {
444 return mShowAirplaneMode
445 ? getContext().getString(R.string.airplane_mode) : "";
446 }
447
448 /**
449 * Top-level function for creating carrier text. Makes text based on simState, PLMN
450 * and SPN as well as device capabilities, such as being emergency call capable.
451 *
452 * @return Carrier text if not in missing state, null otherwise.
453 */
454 private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
455 CharSequence text) {
456 CharSequence carrierText = null;
457 CarrierTextController.StatusMode status = getStatusForIccState(simState);
458 switch (status) {
459 case Normal:
460 carrierText = text;
461 break;
462
463 case SimNotReady:
464 // Null is reserved for denoting missing, in this case we have nothing to display.
465 carrierText = ""; // nothing to display yet.
466 break;
467
468 case NetworkLocked:
469 carrierText = makeCarrierStringOnEmergencyCapable(
470 mContext.getText(R.string.keyguard_network_locked_message), text);
471 break;
472
473 case SimMissing:
474 carrierText = null;
475 break;
476
477 case SimPermDisabled:
478 carrierText = makeCarrierStringOnEmergencyCapable(
479 getContext().getText(
480 R.string.keyguard_permanent_disabled_sim_message_short),
481 text);
482 break;
483
484 case SimMissingLocked:
485 carrierText = null;
486 break;
487
488 case SimLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400489 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500490 getContext().getText(R.string.keyguard_sim_locked_message),
491 text);
492 break;
493
494 case SimPukLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400495 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500496 getContext().getText(R.string.keyguard_sim_puk_locked_message),
497 text);
498 break;
499 case SimIoError:
500 carrierText = makeCarrierStringOnEmergencyCapable(
501 getContext().getText(R.string.keyguard_sim_error_message_short),
502 text);
503 break;
504 case SimUnknown:
505 carrierText = null;
506 break;
507 }
508
509 return carrierText;
510 }
511
512 /*
513 * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
514 */
515 private CharSequence makeCarrierStringOnEmergencyCapable(
516 CharSequence simMessage, CharSequence emergencyCallMessage) {
517 if (mIsEmergencyCallCapable) {
518 return concatenate(simMessage, emergencyCallMessage, mSeparator);
519 }
520 return simMessage;
521 }
522
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400523 /*
524 * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
525 * DSDS
526 */
527 private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
528 CharSequence carrierName) {
529 final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
530 final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
531 if (simMessageValid && carrierNameValid) {
532 return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
533 carrierName, simMessage);
534 } else if (simMessageValid) {
535 return simMessage;
536 } else if (carrierNameValid) {
537 return carrierName;
538 } else {
539 return "";
540 }
541 }
542
Fabian Kozynski02941af2019-01-17 17:57:37 -0500543 /**
544 * Determine the current status of the lock screen given the SIM state and other stuff.
545 */
546 private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
547 // Since reading the SIM may take a while, we assume it is present until told otherwise.
548 if (simState == null) {
549 return CarrierTextController.StatusMode.Normal;
550 }
551
552 final boolean missingAndNotProvisioned =
553 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
554 && (simState == IccCardConstants.State.ABSENT
555 || simState == IccCardConstants.State.PERM_DISABLED);
556
557 // Assume we're NETWORK_LOCKED if not provisioned
558 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
559 switch (simState) {
560 case ABSENT:
561 return CarrierTextController.StatusMode.SimMissing;
562 case NETWORK_LOCKED:
563 return CarrierTextController.StatusMode.SimMissingLocked;
564 case NOT_READY:
565 return CarrierTextController.StatusMode.SimNotReady;
566 case PIN_REQUIRED:
567 return CarrierTextController.StatusMode.SimLocked;
568 case PUK_REQUIRED:
569 return CarrierTextController.StatusMode.SimPukLocked;
570 case READY:
571 return CarrierTextController.StatusMode.Normal;
572 case PERM_DISABLED:
573 return CarrierTextController.StatusMode.SimPermDisabled;
574 case UNKNOWN:
575 return CarrierTextController.StatusMode.SimUnknown;
576 case CARD_IO_ERROR:
577 return CarrierTextController.StatusMode.SimIoError;
578 }
579 return CarrierTextController.StatusMode.SimUnknown;
580 }
581
582 private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
583 CharSequence separator) {
584 final boolean plmnValid = !TextUtils.isEmpty(plmn);
585 final boolean spnValid = !TextUtils.isEmpty(spn);
586 if (plmnValid && spnValid) {
587 return new StringBuilder().append(plmn).append(separator).append(spn).toString();
588 } else if (plmnValid) {
589 return plmn;
590 } else if (spnValid) {
591 return spn;
592 } else {
593 return "";
594 }
595 }
596
Fabian Kozynski00d02f12019-04-15 09:48:30 -0400597 /**
598 * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
599 * separator added so there are no extra separators that are not needed.
600 */
601 private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
602 int length = sequences.length;
603 if (length == 0) return "";
604 StringBuilder sb = new StringBuilder();
605 for (int i = 0; i < length; i++) {
606 if (!TextUtils.isEmpty(sequences[i])) {
607 if (!TextUtils.isEmpty(sb)) {
608 sb.append(separator);
609 }
610 sb.append(sequences[i]);
611 }
612 }
613 return sb.toString();
614 }
615
Fabian Kozynski02941af2019-01-17 17:57:37 -0500616 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
617 if (!TextUtils.isEmpty(string)) {
618 list.add(string);
619 }
620 return list;
621 }
622
623 private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
624 String plmn, String spn) {
625 int carrierHelpTextId = 0;
626 CarrierTextController.StatusMode status = getStatusForIccState(simState);
627 switch (status) {
628 case NetworkLocked:
629 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
630 break;
631
632 case SimMissing:
633 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
634 break;
635
636 case SimPermDisabled:
637 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
638 break;
639
640 case SimMissingLocked:
641 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
642 break;
643
644 case Normal:
645 case SimLocked:
646 case SimPukLocked:
647 break;
648 }
649
650 return mContext.getText(carrierHelpTextId);
651 }
652
653 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500654 * Data structure for passing information to CarrierTextController subscribers
655 */
656 public static final class CarrierTextCallbackInfo {
657 public final CharSequence carrierText;
658 public final CharSequence[] listOfCarriers;
659 public final boolean anySimReady;
660 public final int[] subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400661 public boolean airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500662
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500663 @VisibleForTesting
664 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
Fabian Kozynski1823f112019-01-18 11:43:29 -0500665 boolean anySimReady, int[] subscriptionIds) {
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400666 this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
667 }
668
669 @VisibleForTesting
670 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
671 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
Fabian Kozynski1823f112019-01-18 11:43:29 -0500672 this.carrierText = carrierText;
673 this.listOfCarriers = listOfCarriers;
674 this.anySimReady = anySimReady;
675 this.subscriptionIds = subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400676 this.airplaneMode = airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500677 }
678 }
679
680 /**
Fabian Kozynski02941af2019-01-17 17:57:37 -0500681 * Callback to communicate to Views
682 */
683 public interface CarrierTextCallback {
684 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500685 * Provides updated carrier information.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500686 */
Fabian Kozynski1823f112019-01-18 11:43:29 -0500687 default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
Fabian Kozynski02941af2019-01-17 17:57:37 -0500688
689 /**
690 * Notifies the View that the device is going to sleep
691 */
692 default void startedGoingToSleep() {};
693
694 /**
695 * Notifies the View that the device finished waking up
696 */
697 default void finishedWakingUp() {};
698 }
699}