blob: b1e14346c3fab138084cb6da458918b3ee12b359 [file] [log] [blame]
Fabian Kozynski02941af2019-01-17 17:57:37 -05001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.keyguard;
18
Malcolm Chen06ec3572019-04-09 15:55:29 -070019import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
20import static android.telephony.PhoneStateListener.LISTEN_NONE;
21
Fabian Kozynski02941af2019-01-17 17:57:37 -050022import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
Dave Mankoffc4fe6c42019-11-18 17:03:29 -050025import android.content.res.Resources;
Fabian Kozynski02941af2019-01-17 17:57:37 -050026import android.net.ConnectivityManager;
27import android.net.wifi.WifiManager;
Fabian Kozynskibf6fef32019-02-04 09:21:38 -050028import android.os.Handler;
Malcolm Chen06ec3572019-04-09 15:55:29 -070029import android.telephony.PhoneStateListener;
Fabian Kozynski02941af2019-01-17 17:57:37 -050030import android.telephony.ServiceState;
31import android.telephony.SubscriptionInfo;
Malcolm Chen06ec3572019-04-09 15:55:29 -070032import android.telephony.SubscriptionManager;
Fabian Kozynski02941af2019-01-17 17:57:37 -050033import android.telephony.TelephonyManager;
34import android.text.TextUtils;
35import android.util.Log;
36
Fabian Kozynskid4c84af2019-10-07 09:45:50 -040037import androidx.annotation.Nullable;
Lucas Dupin8968f6a2019-08-09 17:41:15 -070038import androidx.annotation.VisibleForTesting;
39
Fabian Kozynski02941af2019-01-17 17:57:37 -050040import com.android.settingslib.WirelessUtils;
41import com.android.systemui.Dependency;
Hyunyoung Song8f9d34c2019-08-30 14:47:43 -070042import com.android.systemui.R;
Dave Mankoff00e8a2f2019-12-18 16:59:49 -050043import com.android.systemui.dagger.qualifiers.Main;
Fabian Kozynski02941af2019-01-17 17:57:37 -050044import com.android.systemui.keyguard.WakefulnessLifecycle;
45
46import java.util.List;
47import java.util.Objects;
Fabian Kozynski49325ea2020-05-12 12:02:40 -040048import java.util.concurrent.atomic.AtomicBoolean;
Fabian Kozynski02941af2019-01-17 17:57:37 -050049
Dave Mankoffc4fe6c42019-11-18 17:03:29 -050050import javax.inject.Inject;
51
Fabian Kozynski02941af2019-01-17 17:57:37 -050052/**
53 * Controller that generates text including the carrier names and/or the status of all the SIM
54 * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
55 * separated by a given separator {@link CharSequence}.
56 */
57public class CarrierTextController {
58 private static final boolean DEBUG = KeyguardConstants.DEBUG;
59 private static final String TAG = "CarrierTextController";
60
61 private final boolean mIsEmergencyCallCapable;
Fabian Kozynskiba46d1c2019-10-03 12:01:38 -040062 private final Handler mMainHandler;
Fabian Kozynski49325ea2020-05-12 12:02:40 -040063 private final Handler mBgHandler;
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 Kozynski49325ea2020-05-12 12:02:40 -040067 private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
Fabian Kozynskib176f422019-02-05 09:36:59 -050068 @VisibleForTesting
69 protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
Fabian Kozynski02941af2019-01-17 17:57:37 -050070 private WifiManager mWifiManager;
Fabian Kozynskib176f422019-02-05 09:36:59 -050071 private boolean[] mSimErrorState;
72 private final int mSimSlotsNumber;
Fabian Kozynskid4c84af2019-10-07 09:45:50 -040073 @Nullable // Check for nullability before dispatching
Fabian Kozynski02941af2019-01-17 17:57:37 -050074 private CarrierTextCallback mCarrierTextCallback;
75 private Context mContext;
76 private CharSequence mSeparator;
77 private WakefulnessLifecycle mWakefulnessLifecycle;
78 private final WakefulnessLifecycle.Observer mWakefulnessObserver =
79 new WakefulnessLifecycle.Observer() {
80 @Override
81 public void onFinishedWakingUp() {
Fabian Kozynski49325ea2020-05-12 12:02:40 -040082 final CarrierTextCallback callback = mCarrierTextCallback;
83 if (callback != null) callback.finishedWakingUp();
Fabian Kozynski02941af2019-01-17 17:57:37 -050084 }
85
86 @Override
87 public void onStartedGoingToSleep() {
Fabian Kozynski49325ea2020-05-12 12:02:40 -040088 final CarrierTextCallback callback = mCarrierTextCallback;
89 if (callback != null) callback.startedGoingToSleep();
Fabian Kozynski02941af2019-01-17 17:57:37 -050090 }
91 };
92
Fabian Kozynskib176f422019-02-05 09:36:59 -050093 @VisibleForTesting
94 protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
Fabian Kozynski02941af2019-01-17 17:57:37 -050095 @Override
96 public void onRefreshCarrierInfo() {
97 if (DEBUG) {
98 Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
99 + Boolean.toString(mTelephonyCapable));
100 }
101 updateCarrierText();
102 }
103
104 @Override
105 public void onTelephonyCapable(boolean capable) {
106 if (DEBUG) {
107 Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
108 + Boolean.toString(capable));
109 }
110 mTelephonyCapable = capable;
111 updateCarrierText();
112 }
113
Jayachandran Cf5436a62019-11-08 18:22:45 -0800114 public void onSimStateChanged(int subId, int slotId, int simState) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500115 if (slotId < 0 || slotId >= mSimSlotsNumber) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500116 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
117 + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
118 return;
119 }
120
121 if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
122 if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
123 mSimErrorState[slotId] = true;
124 updateCarrierText();
125 } else if (mSimErrorState[slotId]) {
126 mSimErrorState[slotId] = false;
127 updateCarrierText();
128 }
129 }
130 };
131
Malcolm Chen06ec3572019-04-09 15:55:29 -0700132 private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
133 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
134 @Override
135 public void onActiveDataSubscriptionIdChanged(int subId) {
136 mActiveMobileDataSubscription = subId;
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400137 if (mNetworkSupported.get() && mCarrierTextCallback != null) {
Malcolm Chen06ec3572019-04-09 15:55:29 -0700138 updateCarrierText();
139 }
140 }
141 };
142
Fabian Kozynski02941af2019-01-17 17:57:37 -0500143 /**
144 * The status of this lock screen. Primarily used for widgets on LockScreen.
145 */
146 private enum StatusMode {
147 Normal, // Normal case (sim card present, it's not locked)
148 NetworkLocked, // SIM card is 'network locked'.
149 SimMissing, // SIM card is missing.
150 SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
151 SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
152 SimLocked, // SIM card is currently locked
153 SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
154 SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
155 SimIoError, // SIM card is faulty
156 SimUnknown // SIM card is unknown
157 }
158
159 /**
160 * Controller that provides updates on text with carriers names or SIM status.
161 * Used by {@link CarrierText}.
Fabian Kozynski1823f112019-01-18 11:43:29 -0500162 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500163 * @param separator Separator between different parts of the text
Fabian Kozynski02941af2019-01-17 17:57:37 -0500164 */
165 public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
166 boolean showMissingSim) {
167 mContext = context;
Michele5dbecd92019-12-11 17:48:29 -0800168 mIsEmergencyCallCapable = getTelephonyManager().isVoiceCapable();
Fabian Kozynski02941af2019-01-17 17:57:37 -0500169
170 mShowAirplaneMode = showAirplaneMode;
171 mShowMissingSim = showMissingSim;
172
173 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
174 mSeparator = separator;
175 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
Malcolm Chenb908f9c2019-11-04 16:26:28 -0800176 mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
Fabian Kozynskib176f422019-02-05 09:36:59 -0500177 mSimErrorState = new boolean[mSimSlotsNumber];
Fabian Kozynskiba46d1c2019-10-03 12:01:38 -0400178 mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400179 mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
180 mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
181 mBgHandler.post(() -> {
182 boolean supported = ConnectivityManager.from(mContext).isNetworkSupported(
183 ConnectivityManager.TYPE_MOBILE);
184 if (supported && mNetworkSupported.compareAndSet(false, supported)) {
185 // This will set/remove the listeners appropriately. Note that it will never double
186 // add the listeners.
187 handleSetListening(mCarrierTextCallback);
188 }
189 });
Fabian Kozynski02941af2019-01-17 17:57:37 -0500190 }
191
Malcolm Chenb908f9c2019-11-04 16:26:28 -0800192 private TelephonyManager getTelephonyManager() {
193 return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
194 }
195
Fabian Kozynski02941af2019-01-17 17:57:37 -0500196 /**
197 * Checks if there are faulty cards. Adds the text depending on the slot of the card
198 *
199 * @param text: current carrier text based on the sim state
Fabian Kozynskib176f422019-02-05 09:36:59 -0500200 * @param carrierNames names order by subscription order
201 * @param subOrderBySlot array containing the sub index for each slot ID
Fabian Kozynski02941af2019-01-17 17:57:37 -0500202 * @param noSims: whether a valid sim card is inserted
203 * @return text
204 */
Fabian Kozynskib176f422019-02-05 09:36:59 -0500205 private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
206 CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500207 final CharSequence carrier = "";
208 CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
Jayachandran Cf5436a62019-11-08 18:22:45 -0800209 TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500210 // mSimErrorState has the state of each sim indexed by slotID.
Malcolm Chenb908f9c2019-11-04 16:26:28 -0800211 for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
Fabian Kozynskib176f422019-02-05 09:36:59 -0500212 if (!mSimErrorState[index]) {
213 continue;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500214 }
Fabian Kozynskib176f422019-02-05 09:36:59 -0500215 // In the case when no sim cards are detected but a faulty card is inserted
216 // overwrite the text and only show "Invalid card"
217 if (noSims) {
218 return concatenate(carrierTextForSimIOError,
219 getContext().getText(
220 com.android.internal.R.string.emergency_calls_only),
221 mSeparator);
222 } else if (subOrderBySlot[index] != -1) {
223 int subIndex = subOrderBySlot[index];
224 // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
225 carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
226 carrierNames[subIndex],
227 mSeparator);
228 } else {
229 // concatenate "Invalid card" when faulty card is inserted in other slot
230 text = concatenate(text, carrierTextForSimIOError, mSeparator);
231 }
232
Fabian Kozynski02941af2019-01-17 17:57:37 -0500233 }
234 return text;
235 }
236
237 /**
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400238 * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
239 * (assumed false to start). In that case, the following happens:
240 * <ul>
241 * <li> If there was a registered callback, and the network is supported, it will register
242 * listeners.
243 * <li> If there was not a registered callback, it will try to remove unregistered listeners
244 * which is a no-op
245 * </ul>
Fabian Kozynski1823f112019-01-18 11:43:29 -0500246 *
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400247 * This call will always be processed in a background thread.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500248 */
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400249 private void handleSetListening(CarrierTextCallback callback) {
Malcolm Chenb908f9c2019-11-04 16:26:28 -0800250 TelephonyManager telephonyManager = getTelephonyManager();
Fabian Kozynski02941af2019-01-17 17:57:37 -0500251 if (callback != null) {
252 mCarrierTextCallback = callback;
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400253 if (mNetworkSupported.get()) {
Fabian Kozynskiba46d1c2019-10-03 12:01:38 -0400254 // Keyguard update monitor expects callbacks from main thread
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400255 mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500256 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
Malcolm Chen06ec3572019-04-09 15:55:29 -0700257 telephonyManager.listen(mPhoneStateListener,
258 LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500259 } else {
260 // Don't listen and clear out the text when the device isn't a phone.
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400261 mMainHandler.post(() -> callback.updateCarrierInfo(
262 new CarrierTextCallbackInfo("", null, false, null)
263 ));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500264 }
265 } else {
266 mCarrierTextCallback = null;
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400267 mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
268 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
Malcolm Chen06ec3572019-04-09 15:55:29 -0700269 telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
270 }
271 }
272
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400273 /**
274 * Sets the listening status of this controller. If the callback is null, it is set to
275 * not listening.
276 *
277 * @param callback Callback to provide text updates
278 */
279 public void setListening(CarrierTextCallback callback) {
280 mBgHandler.post(() -> handleSetListening(callback));
281 }
282
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700283 protected List<SubscriptionInfo> getSubscriptionInfo() {
Malcolm Chen5c63b512019-08-13 13:24:07 -0700284 return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700285 }
286
Fabian Kozynski02941af2019-01-17 17:57:37 -0500287 protected void updateCarrierText() {
288 boolean allSimsMissing = true;
289 boolean anySimReadyAndInService = false;
290 CharSequence displayText = null;
Sooraj Sasindran0d45da72019-04-25 15:12:21 -0700291 List<SubscriptionInfo> subs = getSubscriptionInfo();
Malcolm Chen06ec3572019-04-09 15:55:29 -0700292
Fabian Kozynski02941af2019-01-17 17:57:37 -0500293 final int numSubs = subs.size();
Fabian Kozynski1823f112019-01-18 11:43:29 -0500294 final int[] subsIds = new int[numSubs];
Fabian Kozynskib176f422019-02-05 09:36:59 -0500295 // This array will contain in position i, the index of subscription in slot ID i.
296 // -1 if no subscription in that slot
297 final int[] subOrderBySlot = new int[mSimSlotsNumber];
298 for (int i = 0; i < mSimSlotsNumber; i++) {
299 subOrderBySlot[i] = -1;
300 }
301 final CharSequence[] carrierNames = new CharSequence[numSubs];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500302 if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500303
Fabian Kozynski02941af2019-01-17 17:57:37 -0500304 for (int i = 0; i < numSubs; i++) {
305 int subId = subs.get(i).getSubscriptionId();
Fabian Kozynskib176f422019-02-05 09:36:59 -0500306 carrierNames[i] = "";
Fabian Kozynski1823f112019-01-18 11:43:29 -0500307 subsIds[i] = subId;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500308 subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
Jayachandran Cf5436a62019-11-08 18:22:45 -0800309 int simState = mKeyguardUpdateMonitor.getSimState(subId);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500310 CharSequence carrierName = subs.get(i).getCarrierName();
311 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
312 if (DEBUG) {
313 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
314 }
315 if (carrierTextForSimState != null) {
316 allSimsMissing = false;
Fabian Kozynskib176f422019-02-05 09:36:59 -0500317 carrierNames[i] = carrierTextForSimState;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500318 }
Jayachandran Cf5436a62019-11-08 18:22:45 -0800319 if (simState == TelephonyManager.SIM_STATE_READY) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000320 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
SongFerngWang2670cba2019-12-23 19:21:14 +0800321 if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500322 // hack for WFC (IWLAN) not turning off immediately once
323 // Wi-Fi is disassociated or disabled
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000324 if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
Fabian Kozynski02941af2019-01-17 17:57:37 -0500325 || (mWifiManager.isWifiEnabled()
326 && mWifiManager.getConnectionInfo() != null
327 && mWifiManager.getConnectionInfo().getBSSID() != null)) {
328 if (DEBUG) {
Bonian Chena7e9e8c2019-06-02 23:59:31 +0000329 Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500330 }
331 anySimReadyAndInService = true;
332 }
333 }
334 }
335 }
Fabian Kozynski0f49f202019-08-29 09:47:07 -0400336 // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
337 // This condition will also be true always when numSubs == 0
338 if (allSimsMissing && !anySimReadyAndInService) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500339 if (numSubs != 0) {
340 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
341 // This depends on mPlmn containing the text "Emergency calls only" when the radio
342 // has some connectivity. Otherwise, it should be null or empty and just show
343 // "No SIM card"
344 // Grab the first subscripton, because they all should contain the emergency text,
345 // described above.
346 displayText = makeCarrierStringOnEmergencyCapable(
347 getMissingSimMessage(), subs.get(0).getCarrierName());
348 } else {
349 // We don't have a SubscriptionInfo to get the emergency calls only from.
350 // Grab it from the old sticky broadcast if possible instead. We can use it
351 // here because no subscriptions are active, so we don't have
352 // to worry about MSIM clashing.
353 CharSequence text =
354 getContext().getText(com.android.internal.R.string.emergency_calls_only);
355 Intent i = getContext().registerReceiver(null,
Meng Wang57f56552020-01-17 16:58:40 -0800356 new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500357 if (i != null) {
358 String spn = "";
359 String plmn = "";
Meng Wang57f56552020-01-17 16:58:40 -0800360 if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
361 spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500362 }
Meng Wang57f56552020-01-17 16:58:40 -0800363 if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
364 plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500365 }
366 if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
367 if (Objects.equals(plmn, spn)) {
368 text = plmn;
369 } else {
370 text = concatenate(plmn, spn, mSeparator);
371 }
372 }
373 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
374 }
375 }
376
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400377 if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
378
Fabian Kozynskib176f422019-02-05 09:36:59 -0500379 displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
380 allSimsMissing);
Fabian Kozynskic3d06f32019-07-31 14:18:41 -0400381
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400382 boolean airplaneMode = false;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500383 // APM (airplane mode) != no carrier state. There are carrier services
384 // (e.g. WFC = Wi-Fi calling) which may operate in APM.
385 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
386 displayText = getAirplaneModeMessage();
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400387 airplaneMode = true;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500388 }
389
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500390 final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
391 displayText,
Fabian Kozynskib176f422019-02-05 09:36:59 -0500392 carrierNames,
393 !allSimsMissing,
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400394 subsIds,
395 airplaneMode);
Fabian Kozynskib176f422019-02-05 09:36:59 -0500396 postToCallback(info);
397 }
398
399 @VisibleForTesting
400 protected void postToCallback(CarrierTextCallbackInfo info) {
Fabian Kozynski48343c32019-02-07 10:28:50 -0500401 final CarrierTextCallback callback = mCarrierTextCallback;
402 if (callback != null) {
Fabian Kozynskiba46d1c2019-10-03 12:01:38 -0400403 mMainHandler.post(() -> callback.updateCarrierInfo(info));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500404 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500405 }
406
407 private Context getContext() {
408 return mContext;
409 }
410
411 private String getMissingSimMessage() {
412 return mShowMissingSim && mTelephonyCapable
413 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
414 }
415
416 private String getAirplaneModeMessage() {
417 return mShowAirplaneMode
418 ? getContext().getString(R.string.airplane_mode) : "";
419 }
420
421 /**
422 * Top-level function for creating carrier text. Makes text based on simState, PLMN
423 * and SPN as well as device capabilities, such as being emergency call capable.
424 *
425 * @return Carrier text if not in missing state, null otherwise.
426 */
Jayachandran Cf5436a62019-11-08 18:22:45 -0800427 private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500428 CharSequence carrierText = null;
429 CarrierTextController.StatusMode status = getStatusForIccState(simState);
430 switch (status) {
431 case Normal:
432 carrierText = text;
433 break;
434
435 case SimNotReady:
436 // Null is reserved for denoting missing, in this case we have nothing to display.
437 carrierText = ""; // nothing to display yet.
438 break;
439
440 case NetworkLocked:
441 carrierText = makeCarrierStringOnEmergencyCapable(
442 mContext.getText(R.string.keyguard_network_locked_message), text);
443 break;
444
445 case SimMissing:
446 carrierText = null;
447 break;
448
449 case SimPermDisabled:
450 carrierText = makeCarrierStringOnEmergencyCapable(
451 getContext().getText(
452 R.string.keyguard_permanent_disabled_sim_message_short),
453 text);
454 break;
455
456 case SimMissingLocked:
457 carrierText = null;
458 break;
459
460 case SimLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400461 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500462 getContext().getText(R.string.keyguard_sim_locked_message),
463 text);
464 break;
465
466 case SimPukLocked:
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400467 carrierText = makeCarrierStringOnLocked(
Fabian Kozynski02941af2019-01-17 17:57:37 -0500468 getContext().getText(R.string.keyguard_sim_puk_locked_message),
469 text);
470 break;
471 case SimIoError:
472 carrierText = makeCarrierStringOnEmergencyCapable(
473 getContext().getText(R.string.keyguard_sim_error_message_short),
474 text);
475 break;
476 case SimUnknown:
477 carrierText = null;
478 break;
479 }
480
481 return carrierText;
482 }
483
484 /*
485 * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
486 */
487 private CharSequence makeCarrierStringOnEmergencyCapable(
488 CharSequence simMessage, CharSequence emergencyCallMessage) {
489 if (mIsEmergencyCallCapable) {
490 return concatenate(simMessage, emergencyCallMessage, mSeparator);
491 }
492 return simMessage;
493 }
494
Fabian Kozynski2fb343a2019-05-08 16:06:51 -0400495 /*
496 * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
497 * DSDS
498 */
499 private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
500 CharSequence carrierName) {
501 final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
502 final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
503 if (simMessageValid && carrierNameValid) {
504 return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
505 carrierName, simMessage);
506 } else if (simMessageValid) {
507 return simMessage;
508 } else if (carrierNameValid) {
509 return carrierName;
510 } else {
511 return "";
512 }
513 }
514
Fabian Kozynski02941af2019-01-17 17:57:37 -0500515 /**
516 * Determine the current status of the lock screen given the SIM state and other stuff.
517 */
Jayachandran Cf5436a62019-11-08 18:22:45 -0800518 private CarrierTextController.StatusMode getStatusForIccState(int simState) {
Fabian Kozynski02941af2019-01-17 17:57:37 -0500519 final boolean missingAndNotProvisioned =
Fabian Kozynski49325ea2020-05-12 12:02:40 -0400520 !mKeyguardUpdateMonitor.isDeviceProvisioned()
Jayachandran Cf5436a62019-11-08 18:22:45 -0800521 && (simState == TelephonyManager.SIM_STATE_ABSENT
522 || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
Fabian Kozynski02941af2019-01-17 17:57:37 -0500523
524 // Assume we're NETWORK_LOCKED if not provisioned
Jayachandran Cf5436a62019-11-08 18:22:45 -0800525 simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500526 switch (simState) {
Jayachandran Cf5436a62019-11-08 18:22:45 -0800527 case TelephonyManager.SIM_STATE_ABSENT:
Fabian Kozynski02941af2019-01-17 17:57:37 -0500528 return CarrierTextController.StatusMode.SimMissing;
Jayachandran Cf5436a62019-11-08 18:22:45 -0800529 case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
Fabian Kozynski02941af2019-01-17 17:57:37 -0500530 return CarrierTextController.StatusMode.SimMissingLocked;
Jayachandran Cf5436a62019-11-08 18:22:45 -0800531 case TelephonyManager.SIM_STATE_NOT_READY:
Fabian Kozynski02941af2019-01-17 17:57:37 -0500532 return CarrierTextController.StatusMode.SimNotReady;
Jayachandran Cf5436a62019-11-08 18:22:45 -0800533 case TelephonyManager.SIM_STATE_PIN_REQUIRED:
Fabian Kozynski02941af2019-01-17 17:57:37 -0500534 return CarrierTextController.StatusMode.SimLocked;
Jayachandran Cf5436a62019-11-08 18:22:45 -0800535 case TelephonyManager.SIM_STATE_PUK_REQUIRED:
Fabian Kozynski02941af2019-01-17 17:57:37 -0500536 return CarrierTextController.StatusMode.SimPukLocked;
Jayachandran Cf5436a62019-11-08 18:22:45 -0800537 case TelephonyManager.SIM_STATE_READY:
Fabian Kozynski02941af2019-01-17 17:57:37 -0500538 return CarrierTextController.StatusMode.Normal;
Jayachandran Cf5436a62019-11-08 18:22:45 -0800539 case TelephonyManager.SIM_STATE_PERM_DISABLED:
Fabian Kozynski02941af2019-01-17 17:57:37 -0500540 return CarrierTextController.StatusMode.SimPermDisabled;
Jayachandran Cf5436a62019-11-08 18:22:45 -0800541 case TelephonyManager.SIM_STATE_UNKNOWN:
Fabian Kozynski02941af2019-01-17 17:57:37 -0500542 return CarrierTextController.StatusMode.SimUnknown;
Jayachandran Cf5436a62019-11-08 18:22:45 -0800543 case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
Fabian Kozynski02941af2019-01-17 17:57:37 -0500544 return CarrierTextController.StatusMode.SimIoError;
545 }
546 return CarrierTextController.StatusMode.SimUnknown;
547 }
548
549 private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
550 CharSequence separator) {
551 final boolean plmnValid = !TextUtils.isEmpty(plmn);
552 final boolean spnValid = !TextUtils.isEmpty(spn);
553 if (plmnValid && spnValid) {
554 return new StringBuilder().append(plmn).append(separator).append(spn).toString();
555 } else if (plmnValid) {
556 return plmn;
557 } else if (spnValid) {
558 return spn;
559 } else {
560 return "";
561 }
562 }
563
Fabian Kozynski00d02f12019-04-15 09:48:30 -0400564 /**
565 * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
566 * separator added so there are no extra separators that are not needed.
567 */
568 private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
569 int length = sequences.length;
570 if (length == 0) return "";
571 StringBuilder sb = new StringBuilder();
572 for (int i = 0; i < length; i++) {
573 if (!TextUtils.isEmpty(sequences[i])) {
574 if (!TextUtils.isEmpty(sb)) {
575 sb.append(separator);
576 }
577 sb.append(sequences[i]);
578 }
579 }
580 return sb.toString();
581 }
582
Fabian Kozynski02941af2019-01-17 17:57:37 -0500583 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
584 if (!TextUtils.isEmpty(string)) {
585 list.add(string);
586 }
587 return list;
588 }
589
Jayachandran Cf5436a62019-11-08 18:22:45 -0800590 private CharSequence getCarrierHelpTextForSimState(int simState,
Fabian Kozynski02941af2019-01-17 17:57:37 -0500591 String plmn, String spn) {
592 int carrierHelpTextId = 0;
593 CarrierTextController.StatusMode status = getStatusForIccState(simState);
594 switch (status) {
595 case NetworkLocked:
596 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
597 break;
598
599 case SimMissing:
600 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
601 break;
602
603 case SimPermDisabled:
604 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
605 break;
606
607 case SimMissingLocked:
608 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
609 break;
610
611 case Normal:
612 case SimLocked:
613 case SimPukLocked:
614 break;
615 }
616
617 return mContext.getText(carrierHelpTextId);
618 }
619
Dave Mankoffc4fe6c42019-11-18 17:03:29 -0500620 public static class Builder {
621 private final Context mContext;
622 private final String mSeparator;
623 private boolean mShowAirplaneMode;
624 private boolean mShowMissingSim;
625
626 @Inject
Dave Mankoff00e8a2f2019-12-18 16:59:49 -0500627 public Builder(Context context, @Main Resources resources) {
Dave Mankoffc4fe6c42019-11-18 17:03:29 -0500628 mContext = context;
629 mSeparator = resources.getString(
630 com.android.internal.R.string.kg_text_message_separator);
631 }
632
633
634 public Builder setShowAirplaneMode(boolean showAirplaneMode) {
635 mShowAirplaneMode = showAirplaneMode;
636 return this;
637 }
638
639 public Builder setShowMissingSim(boolean showMissingSim) {
640 mShowMissingSim = showMissingSim;
641 return this;
642 }
643
644 public CarrierTextController build() {
645 return new CarrierTextController(
646 mContext, mSeparator, mShowAirplaneMode, mShowMissingSim);
647 }
648 }
Fabian Kozynski02941af2019-01-17 17:57:37 -0500649 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500650 * Data structure for passing information to CarrierTextController subscribers
651 */
652 public static final class CarrierTextCallbackInfo {
653 public final CharSequence carrierText;
654 public final CharSequence[] listOfCarriers;
655 public final boolean anySimReady;
656 public final int[] subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400657 public boolean airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500658
Fabian Kozynskibf6fef32019-02-04 09:21:38 -0500659 @VisibleForTesting
660 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
Fabian Kozynski1823f112019-01-18 11:43:29 -0500661 boolean anySimReady, int[] subscriptionIds) {
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400662 this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
663 }
664
665 @VisibleForTesting
666 public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
667 boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
Fabian Kozynski1823f112019-01-18 11:43:29 -0500668 this.carrierText = carrierText;
669 this.listOfCarriers = listOfCarriers;
670 this.anySimReady = anySimReady;
671 this.subscriptionIds = subscriptionIds;
Fabian Kozynskib38edbb2019-04-12 12:20:13 -0400672 this.airplaneMode = airplaneMode;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500673 }
674 }
675
676 /**
Fabian Kozynski02941af2019-01-17 17:57:37 -0500677 * Callback to communicate to Views
678 */
679 public interface CarrierTextCallback {
680 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500681 * Provides updated carrier information.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500682 */
Fabian Kozynski1823f112019-01-18 11:43:29 -0500683 default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
Fabian Kozynski02941af2019-01-17 17:57:37 -0500684
685 /**
686 * Notifies the View that the device is going to sleep
687 */
688 default void startedGoingToSleep() {};
689
690 /**
691 * Notifies the View that the device finished waking up
692 */
693 default void finishedWakingUp() {};
694 }
695}