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