blob: 2ce69650b65c70e7ac6ba5f192f9fb1fdefb19ca [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
19import android.content.Context;
20import android.content.Intent;
21import android.content.IntentFilter;
22import android.net.ConnectivityManager;
23import android.net.wifi.WifiManager;
24import android.telephony.ServiceState;
25import android.telephony.SubscriptionInfo;
26import android.telephony.TelephonyManager;
27import android.text.TextUtils;
28import android.util.Log;
29
30import com.android.internal.telephony.IccCardConstants;
31import com.android.internal.telephony.TelephonyIntents;
32import com.android.settingslib.WirelessUtils;
33import com.android.systemui.Dependency;
34import com.android.systemui.keyguard.WakefulnessLifecycle;
35
36import java.util.List;
37import java.util.Objects;
38
39/**
40 * Controller that generates text including the carrier names and/or the status of all the SIM
41 * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
42 * separated by a given separator {@link CharSequence}.
43 */
44public class CarrierTextController {
45 private static final boolean DEBUG = KeyguardConstants.DEBUG;
46 private static final String TAG = "CarrierTextController";
47
48 private final boolean mIsEmergencyCallCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050049 private boolean mTelephonyCapable;
Fabian Kozynski02941af2019-01-17 17:57:37 -050050 private boolean mShowMissingSim;
Fabian Kozynski02941af2019-01-17 17:57:37 -050051 private boolean mShowAirplaneMode;
Fabian Kozynski02941af2019-01-17 17:57:37 -050052 private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
Fabian Kozynski02941af2019-01-17 17:57:37 -050053 private WifiManager mWifiManager;
Fabian Kozynski02941af2019-01-17 17:57:37 -050054 private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
55 private CarrierTextCallback mCarrierTextCallback;
56 private Context mContext;
57 private CharSequence mSeparator;
58 private WakefulnessLifecycle mWakefulnessLifecycle;
59 private final WakefulnessLifecycle.Observer mWakefulnessObserver =
60 new WakefulnessLifecycle.Observer() {
61 @Override
62 public void onFinishedWakingUp() {
63 mCarrierTextCallback.finishedWakingUp();
64 }
65
66 @Override
67 public void onStartedGoingToSleep() {
68 mCarrierTextCallback.startedGoingToSleep();
69 }
70 };
71
72 private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
73 @Override
74 public void onRefreshCarrierInfo() {
75 if (DEBUG) {
76 Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
77 + Boolean.toString(mTelephonyCapable));
78 }
79 updateCarrierText();
80 }
81
82 @Override
83 public void onTelephonyCapable(boolean capable) {
84 if (DEBUG) {
85 Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
86 + Boolean.toString(capable));
87 }
88 mTelephonyCapable = capable;
89 updateCarrierText();
90 }
91
92 public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
93 if (slotId < 0) {
94 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
95 + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
96 return;
97 }
98
99 if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
100 if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
101 mSimErrorState[slotId] = true;
102 updateCarrierText();
103 } else if (mSimErrorState[slotId]) {
104 mSimErrorState[slotId] = false;
105 updateCarrierText();
106 }
107 }
108 };
109
110 /**
111 * The status of this lock screen. Primarily used for widgets on LockScreen.
112 */
113 private enum StatusMode {
114 Normal, // Normal case (sim card present, it's not locked)
115 NetworkLocked, // SIM card is 'network locked'.
116 SimMissing, // SIM card is missing.
117 SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
118 SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
119 SimLocked, // SIM card is currently locked
120 SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
121 SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
122 SimIoError, // SIM card is faulty
123 SimUnknown // SIM card is unknown
124 }
125
126 /**
127 * Controller that provides updates on text with carriers names or SIM status.
128 * Used by {@link CarrierText}.
Fabian Kozynski1823f112019-01-18 11:43:29 -0500129 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500130 * @param separator Separator between different parts of the text
Fabian Kozynski02941af2019-01-17 17:57:37 -0500131 */
132 public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
133 boolean showMissingSim) {
134 mContext = context;
135 mIsEmergencyCallCapable = context.getResources().getBoolean(
136 com.android.internal.R.bool.config_voice_capable);
137
138 mShowAirplaneMode = showAirplaneMode;
139 mShowMissingSim = showMissingSim;
140
141 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
142 mSeparator = separator;
143 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
144 }
145
146 /**
147 * Checks if there are faulty cards. Adds the text depending on the slot of the card
148 *
149 * @param text: current carrier text based on the sim state
150 * @param noSims: whether a valid sim card is inserted
151 * @return text
152 */
153 private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) {
154 final CharSequence carrier = "";
155 CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
156 IccCardConstants.State.CARD_IO_ERROR, carrier);
157 for (int index = 0; index < mSimErrorState.length; index++) {
158 if (mSimErrorState[index]) {
159 // In the case when no sim cards are detected but a faulty card is inserted
160 // overwrite the text and only show "Invalid card"
161 if (noSims) {
162 return concatenate(carrierTextForSimIOError,
163 getContext().getText(
164 com.android.internal.R.string.emergency_calls_only),
165 mSeparator);
166 } else if (index == 0) {
167 // prepend "Invalid card" when faulty card is inserted in slot 0
168 text = concatenate(carrierTextForSimIOError, text, mSeparator);
169 } else {
170 // concatenate "Invalid card" when faulty card is inserted in slot 1
171 text = concatenate(text, carrierTextForSimIOError, mSeparator);
172 }
173 }
174 }
175 return text;
176 }
177
178 /**
179 * Sets the listening status of this controller. If the callback is null, it is set to
180 * not listening
Fabian Kozynski1823f112019-01-18 11:43:29 -0500181 *
Fabian Kozynski02941af2019-01-17 17:57:37 -0500182 * @param callback Callback to provide text updates
183 */
184 public void setListening(CarrierTextCallback callback) {
185 if (callback != null) {
186 mCarrierTextCallback = callback;
187 if (ConnectivityManager.from(mContext).isNetworkSupported(
188 ConnectivityManager.TYPE_MOBILE)) {
189 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
190 mKeyguardUpdateMonitor.registerCallback(mCallback);
191 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
192 } else {
193 // Don't listen and clear out the text when the device isn't a phone.
194 mKeyguardUpdateMonitor = null;
Fabian Kozynski1823f112019-01-18 11:43:29 -0500195 callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500196 }
197 } else {
198 mCarrierTextCallback = null;
199 if (mKeyguardUpdateMonitor != null) {
200 mKeyguardUpdateMonitor.removeCallback(mCallback);
201 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
202 }
203 }
204 }
205
206 protected void updateCarrierText() {
207 boolean allSimsMissing = true;
208 boolean anySimReadyAndInService = false;
209 CharSequence displayText = null;
210
211 List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
212 final int numSubs = subs.size();
Fabian Kozynski1823f112019-01-18 11:43:29 -0500213 final int[] subsIds = new int[numSubs];
Fabian Kozynski02941af2019-01-17 17:57:37 -0500214 if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
215 for (int i = 0; i < numSubs; i++) {
216 int subId = subs.get(i).getSubscriptionId();
Fabian Kozynski1823f112019-01-18 11:43:29 -0500217 subsIds[i] = subId;
Fabian Kozynski02941af2019-01-17 17:57:37 -0500218 IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
219 CharSequence carrierName = subs.get(i).getCarrierName();
220 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
221 if (DEBUG) {
222 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
223 }
224 if (carrierTextForSimState != null) {
225 allSimsMissing = false;
226 displayText = concatenate(displayText, carrierTextForSimState, mSeparator);
227 }
228 if (simState == IccCardConstants.State.READY) {
229 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
230 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
231 // hack for WFC (IWLAN) not turning off immediately once
232 // Wi-Fi is disassociated or disabled
233 if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
234 || (mWifiManager.isWifiEnabled()
235 && mWifiManager.getConnectionInfo() != null
236 && mWifiManager.getConnectionInfo().getBSSID() != null)) {
237 if (DEBUG) {
238 Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
239 }
240 anySimReadyAndInService = true;
241 }
242 }
243 }
244 }
245 if (allSimsMissing) {
246 if (numSubs != 0) {
247 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
248 // This depends on mPlmn containing the text "Emergency calls only" when the radio
249 // has some connectivity. Otherwise, it should be null or empty and just show
250 // "No SIM card"
251 // Grab the first subscripton, because they all should contain the emergency text,
252 // described above.
253 displayText = makeCarrierStringOnEmergencyCapable(
254 getMissingSimMessage(), subs.get(0).getCarrierName());
255 } else {
256 // We don't have a SubscriptionInfo to get the emergency calls only from.
257 // Grab it from the old sticky broadcast if possible instead. We can use it
258 // here because no subscriptions are active, so we don't have
259 // to worry about MSIM clashing.
260 CharSequence text =
261 getContext().getText(com.android.internal.R.string.emergency_calls_only);
262 Intent i = getContext().registerReceiver(null,
263 new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
264 if (i != null) {
265 String spn = "";
266 String plmn = "";
267 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
268 spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
269 }
270 if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
271 plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
272 }
273 if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
274 if (Objects.equals(plmn, spn)) {
275 text = plmn;
276 } else {
277 text = concatenate(plmn, spn, mSeparator);
278 }
279 }
280 displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
281 }
282 }
283
284 displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing);
285 // APM (airplane mode) != no carrier state. There are carrier services
286 // (e.g. WFC = Wi-Fi calling) which may operate in APM.
287 if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
288 displayText = getAirplaneModeMessage();
289 }
290
291 if (mCarrierTextCallback != null) {
Fabian Kozynski1823f112019-01-18 11:43:29 -0500292 mCarrierTextCallback.updateCarrierInfo(new CarrierTextCallbackInfo(
293 displayText,
294 displayText.toString().split(mSeparator.toString()),
295 anySimReadyAndInService,
296 subsIds));
Fabian Kozynski02941af2019-01-17 17:57:37 -0500297 }
298
299 }
300
301 private Context getContext() {
302 return mContext;
303 }
304
305 private String getMissingSimMessage() {
306 return mShowMissingSim && mTelephonyCapable
307 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
308 }
309
310 private String getAirplaneModeMessage() {
311 return mShowAirplaneMode
312 ? getContext().getString(R.string.airplane_mode) : "";
313 }
314
315 /**
316 * Top-level function for creating carrier text. Makes text based on simState, PLMN
317 * and SPN as well as device capabilities, such as being emergency call capable.
318 *
319 * @return Carrier text if not in missing state, null otherwise.
320 */
321 private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
322 CharSequence text) {
323 CharSequence carrierText = null;
324 CarrierTextController.StatusMode status = getStatusForIccState(simState);
325 switch (status) {
326 case Normal:
327 carrierText = text;
328 break;
329
330 case SimNotReady:
331 // Null is reserved for denoting missing, in this case we have nothing to display.
332 carrierText = ""; // nothing to display yet.
333 break;
334
335 case NetworkLocked:
336 carrierText = makeCarrierStringOnEmergencyCapable(
337 mContext.getText(R.string.keyguard_network_locked_message), text);
338 break;
339
340 case SimMissing:
341 carrierText = null;
342 break;
343
344 case SimPermDisabled:
345 carrierText = makeCarrierStringOnEmergencyCapable(
346 getContext().getText(
347 R.string.keyguard_permanent_disabled_sim_message_short),
348 text);
349 break;
350
351 case SimMissingLocked:
352 carrierText = null;
353 break;
354
355 case SimLocked:
356 carrierText = makeCarrierStringOnEmergencyCapable(
357 getContext().getText(R.string.keyguard_sim_locked_message),
358 text);
359 break;
360
361 case SimPukLocked:
362 carrierText = makeCarrierStringOnEmergencyCapable(
363 getContext().getText(R.string.keyguard_sim_puk_locked_message),
364 text);
365 break;
366 case SimIoError:
367 carrierText = makeCarrierStringOnEmergencyCapable(
368 getContext().getText(R.string.keyguard_sim_error_message_short),
369 text);
370 break;
371 case SimUnknown:
372 carrierText = null;
373 break;
374 }
375
376 return carrierText;
377 }
378
379 /*
380 * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
381 */
382 private CharSequence makeCarrierStringOnEmergencyCapable(
383 CharSequence simMessage, CharSequence emergencyCallMessage) {
384 if (mIsEmergencyCallCapable) {
385 return concatenate(simMessage, emergencyCallMessage, mSeparator);
386 }
387 return simMessage;
388 }
389
390 /**
391 * Determine the current status of the lock screen given the SIM state and other stuff.
392 */
393 private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
394 // Since reading the SIM may take a while, we assume it is present until told otherwise.
395 if (simState == null) {
396 return CarrierTextController.StatusMode.Normal;
397 }
398
399 final boolean missingAndNotProvisioned =
400 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
401 && (simState == IccCardConstants.State.ABSENT
402 || simState == IccCardConstants.State.PERM_DISABLED);
403
404 // Assume we're NETWORK_LOCKED if not provisioned
405 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
406 switch (simState) {
407 case ABSENT:
408 return CarrierTextController.StatusMode.SimMissing;
409 case NETWORK_LOCKED:
410 return CarrierTextController.StatusMode.SimMissingLocked;
411 case NOT_READY:
412 return CarrierTextController.StatusMode.SimNotReady;
413 case PIN_REQUIRED:
414 return CarrierTextController.StatusMode.SimLocked;
415 case PUK_REQUIRED:
416 return CarrierTextController.StatusMode.SimPukLocked;
417 case READY:
418 return CarrierTextController.StatusMode.Normal;
419 case PERM_DISABLED:
420 return CarrierTextController.StatusMode.SimPermDisabled;
421 case UNKNOWN:
422 return CarrierTextController.StatusMode.SimUnknown;
423 case CARD_IO_ERROR:
424 return CarrierTextController.StatusMode.SimIoError;
425 }
426 return CarrierTextController.StatusMode.SimUnknown;
427 }
428
429 private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
430 CharSequence separator) {
431 final boolean plmnValid = !TextUtils.isEmpty(plmn);
432 final boolean spnValid = !TextUtils.isEmpty(spn);
433 if (plmnValid && spnValid) {
434 return new StringBuilder().append(plmn).append(separator).append(spn).toString();
435 } else if (plmnValid) {
436 return plmn;
437 } else if (spnValid) {
438 return spn;
439 } else {
440 return "";
441 }
442 }
443
444 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
445 if (!TextUtils.isEmpty(string)) {
446 list.add(string);
447 }
448 return list;
449 }
450
451 private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
452 String plmn, String spn) {
453 int carrierHelpTextId = 0;
454 CarrierTextController.StatusMode status = getStatusForIccState(simState);
455 switch (status) {
456 case NetworkLocked:
457 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
458 break;
459
460 case SimMissing:
461 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
462 break;
463
464 case SimPermDisabled:
465 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
466 break;
467
468 case SimMissingLocked:
469 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
470 break;
471
472 case Normal:
473 case SimLocked:
474 case SimPukLocked:
475 break;
476 }
477
478 return mContext.getText(carrierHelpTextId);
479 }
480
481 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500482 * Data structure for passing information to CarrierTextController subscribers
483 */
484 public static final class CarrierTextCallbackInfo {
485 public final CharSequence carrierText;
486 public final CharSequence[] listOfCarriers;
487 public final boolean anySimReady;
488 public final int[] subscriptionIds;
489
490 CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
491 boolean anySimReady, int[] subscriptionIds) {
492 this.carrierText = carrierText;
493 this.listOfCarriers = listOfCarriers;
494 this.anySimReady = anySimReady;
495 this.subscriptionIds = subscriptionIds;
496 }
497 }
498
499 /**
Fabian Kozynski02941af2019-01-17 17:57:37 -0500500 * Callback to communicate to Views
501 */
502 public interface CarrierTextCallback {
503 /**
Fabian Kozynski1823f112019-01-18 11:43:29 -0500504 * Provides updated carrier information.
Fabian Kozynski02941af2019-01-17 17:57:37 -0500505 */
Fabian Kozynski1823f112019-01-18 11:43:29 -0500506 default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
Fabian Kozynski02941af2019-01-17 17:57:37 -0500507
508 /**
509 * Notifies the View that the device is going to sleep
510 */
511 default void startedGoingToSleep() {};
512
513 /**
514 * Notifies the View that the device finished waking up
515 */
516 default void finishedWakingUp() {};
517 }
518}