blob: 3698a6e6a776df67e2e44ca84bc68d65fa4cc1a8 [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;
49
50 private boolean mTelephonyCapable;
51
52 private boolean mShowMissingSim;
53
54 private boolean mShowAirplaneMode;
55
56 private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
57
58 private WifiManager mWifiManager;
59
60 private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
61 private CarrierTextCallback mCarrierTextCallback;
62 private Context mContext;
63 private CharSequence mSeparator;
64 private WakefulnessLifecycle mWakefulnessLifecycle;
65 private final WakefulnessLifecycle.Observer mWakefulnessObserver =
66 new WakefulnessLifecycle.Observer() {
67 @Override
68 public void onFinishedWakingUp() {
69 mCarrierTextCallback.finishedWakingUp();
70 }
71
72 @Override
73 public void onStartedGoingToSleep() {
74 mCarrierTextCallback.startedGoingToSleep();
75 }
76 };
77
78 private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
79 @Override
80 public void onRefreshCarrierInfo() {
81 if (DEBUG) {
82 Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
83 + Boolean.toString(mTelephonyCapable));
84 }
85 updateCarrierText();
86 }
87
88 @Override
89 public void onTelephonyCapable(boolean capable) {
90 if (DEBUG) {
91 Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
92 + Boolean.toString(capable));
93 }
94 mTelephonyCapable = capable;
95 updateCarrierText();
96 }
97
98 public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
99 if (slotId < 0) {
100 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
101 + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
102 return;
103 }
104
105 if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
106 if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
107 mSimErrorState[slotId] = true;
108 updateCarrierText();
109 } else if (mSimErrorState[slotId]) {
110 mSimErrorState[slotId] = false;
111 updateCarrierText();
112 }
113 }
114 };
115
116 /**
117 * The status of this lock screen. Primarily used for widgets on LockScreen.
118 */
119 private enum StatusMode {
120 Normal, // Normal case (sim card present, it's not locked)
121 NetworkLocked, // SIM card is 'network locked'.
122 SimMissing, // SIM card is missing.
123 SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
124 SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
125 SimLocked, // SIM card is currently locked
126 SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
127 SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
128 SimIoError, // SIM card is faulty
129 SimUnknown // SIM card is unknown
130 }
131
132 /**
133 * Controller that provides updates on text with carriers names or SIM status.
134 * Used by {@link CarrierText}.
135 * @param context
136 * @param separator Separator between different parts of the text
137 * @param showAirplaneMode
138 * @param showMissingSim
139 */
140 public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
141 boolean showMissingSim) {
142 mContext = context;
143 mIsEmergencyCallCapable = context.getResources().getBoolean(
144 com.android.internal.R.bool.config_voice_capable);
145
146 mShowAirplaneMode = showAirplaneMode;
147 mShowMissingSim = showMissingSim;
148
149 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
150 mSeparator = separator;
151 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
152 }
153
154 /**
155 * Checks if there are faulty cards. Adds the text depending on the slot of the card
156 *
157 * @param text: current carrier text based on the sim state
158 * @param noSims: whether a valid sim card is inserted
159 * @return text
160 */
161 private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) {
162 final CharSequence carrier = "";
163 CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
164 IccCardConstants.State.CARD_IO_ERROR, carrier);
165 for (int index = 0; index < mSimErrorState.length; index++) {
166 if (mSimErrorState[index]) {
167 // In the case when no sim cards are detected but a faulty card is inserted
168 // overwrite the text and only show "Invalid card"
169 if (noSims) {
170 return concatenate(carrierTextForSimIOError,
171 getContext().getText(
172 com.android.internal.R.string.emergency_calls_only),
173 mSeparator);
174 } else if (index == 0) {
175 // prepend "Invalid card" when faulty card is inserted in slot 0
176 text = concatenate(carrierTextForSimIOError, text, mSeparator);
177 } else {
178 // concatenate "Invalid card" when faulty card is inserted in slot 1
179 text = concatenate(text, carrierTextForSimIOError, mSeparator);
180 }
181 }
182 }
183 return text;
184 }
185
186 /**
187 * Sets the listening status of this controller. If the callback is null, it is set to
188 * not listening
189 * @param callback Callback to provide text updates
190 */
191 public void setListening(CarrierTextCallback callback) {
192 if (callback != null) {
193 mCarrierTextCallback = callback;
194 if (ConnectivityManager.from(mContext).isNetworkSupported(
195 ConnectivityManager.TYPE_MOBILE)) {
196 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
197 mKeyguardUpdateMonitor.registerCallback(mCallback);
198 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
199 } else {
200 // Don't listen and clear out the text when the device isn't a phone.
201 mKeyguardUpdateMonitor = null;
202 callback.updateCarrierText("", false);
203 }
204 } else {
205 mCarrierTextCallback = null;
206 if (mKeyguardUpdateMonitor != null) {
207 mKeyguardUpdateMonitor.removeCallback(mCallback);
208 mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
209 }
210 }
211 }
212
213 protected void updateCarrierText() {
214 boolean allSimsMissing = true;
215 boolean anySimReadyAndInService = false;
216 CharSequence displayText = null;
217
218 List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
219 final int numSubs = subs.size();
220 if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
221 for (int i = 0; i < numSubs; i++) {
222 int subId = subs.get(i).getSubscriptionId();
223 IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
224 CharSequence carrierName = subs.get(i).getCarrierName();
225 CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
226 if (DEBUG) {
227 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
228 }
229 if (carrierTextForSimState != null) {
230 allSimsMissing = false;
231 displayText = concatenate(displayText, carrierTextForSimState, mSeparator);
232 }
233 if (simState == IccCardConstants.State.READY) {
234 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
235 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
236 // hack for WFC (IWLAN) not turning off immediately once
237 // Wi-Fi is disassociated or disabled
238 if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
239 || (mWifiManager.isWifiEnabled()
240 && mWifiManager.getConnectionInfo() != null
241 && mWifiManager.getConnectionInfo().getBSSID() != null)) {
242 if (DEBUG) {
243 Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
244 }
245 anySimReadyAndInService = true;
246 }
247 }
248 }
249 }
250 if (allSimsMissing) {
251 if (numSubs != 0) {
252 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
253 // This depends on mPlmn containing the text "Emergency calls only" when the radio
254 // has some connectivity. Otherwise, it should be null or empty and just show
255 // "No SIM card"
256 // Grab the first subscripton, because they all should contain the emergency text,
257 // described above.
258 displayText = makeCarrierStringOnEmergencyCapable(
259 getMissingSimMessage(), subs.get(0).getCarrierName());
260 } 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
296 if (mCarrierTextCallback != null) {
297 mCarrierTextCallback.updateCarrierText(displayText, anySimReadyAndInService);
298 mCarrierTextCallback.updateCarrierList(
299 displayText.toString().split(mSeparator.toString()), anySimReadyAndInService);
300 }
301
302 }
303
304 private Context getContext() {
305 return mContext;
306 }
307
308 private String getMissingSimMessage() {
309 return mShowMissingSim && mTelephonyCapable
310 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
311 }
312
313 private String getAirplaneModeMessage() {
314 return mShowAirplaneMode
315 ? getContext().getString(R.string.airplane_mode) : "";
316 }
317
318 /**
319 * Top-level function for creating carrier text. Makes text based on simState, PLMN
320 * and SPN as well as device capabilities, such as being emergency call capable.
321 *
322 * @return Carrier text if not in missing state, null otherwise.
323 */
324 private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
325 CharSequence text) {
326 CharSequence carrierText = null;
327 CarrierTextController.StatusMode status = getStatusForIccState(simState);
328 switch (status) {
329 case Normal:
330 carrierText = text;
331 break;
332
333 case SimNotReady:
334 // Null is reserved for denoting missing, in this case we have nothing to display.
335 carrierText = ""; // nothing to display yet.
336 break;
337
338 case NetworkLocked:
339 carrierText = makeCarrierStringOnEmergencyCapable(
340 mContext.getText(R.string.keyguard_network_locked_message), text);
341 break;
342
343 case SimMissing:
344 carrierText = null;
345 break;
346
347 case SimPermDisabled:
348 carrierText = makeCarrierStringOnEmergencyCapable(
349 getContext().getText(
350 R.string.keyguard_permanent_disabled_sim_message_short),
351 text);
352 break;
353
354 case SimMissingLocked:
355 carrierText = null;
356 break;
357
358 case SimLocked:
359 carrierText = makeCarrierStringOnEmergencyCapable(
360 getContext().getText(R.string.keyguard_sim_locked_message),
361 text);
362 break;
363
364 case SimPukLocked:
365 carrierText = makeCarrierStringOnEmergencyCapable(
366 getContext().getText(R.string.keyguard_sim_puk_locked_message),
367 text);
368 break;
369 case SimIoError:
370 carrierText = makeCarrierStringOnEmergencyCapable(
371 getContext().getText(R.string.keyguard_sim_error_message_short),
372 text);
373 break;
374 case SimUnknown:
375 carrierText = null;
376 break;
377 }
378
379 return carrierText;
380 }
381
382 /*
383 * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
384 */
385 private CharSequence makeCarrierStringOnEmergencyCapable(
386 CharSequence simMessage, CharSequence emergencyCallMessage) {
387 if (mIsEmergencyCallCapable) {
388 return concatenate(simMessage, emergencyCallMessage, mSeparator);
389 }
390 return simMessage;
391 }
392
393 /**
394 * Determine the current status of the lock screen given the SIM state and other stuff.
395 */
396 private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
397 // Since reading the SIM may take a while, we assume it is present until told otherwise.
398 if (simState == null) {
399 return CarrierTextController.StatusMode.Normal;
400 }
401
402 final boolean missingAndNotProvisioned =
403 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
404 && (simState == IccCardConstants.State.ABSENT
405 || simState == IccCardConstants.State.PERM_DISABLED);
406
407 // Assume we're NETWORK_LOCKED if not provisioned
408 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
409 switch (simState) {
410 case ABSENT:
411 return CarrierTextController.StatusMode.SimMissing;
412 case NETWORK_LOCKED:
413 return CarrierTextController.StatusMode.SimMissingLocked;
414 case NOT_READY:
415 return CarrierTextController.StatusMode.SimNotReady;
416 case PIN_REQUIRED:
417 return CarrierTextController.StatusMode.SimLocked;
418 case PUK_REQUIRED:
419 return CarrierTextController.StatusMode.SimPukLocked;
420 case READY:
421 return CarrierTextController.StatusMode.Normal;
422 case PERM_DISABLED:
423 return CarrierTextController.StatusMode.SimPermDisabled;
424 case UNKNOWN:
425 return CarrierTextController.StatusMode.SimUnknown;
426 case CARD_IO_ERROR:
427 return CarrierTextController.StatusMode.SimIoError;
428 }
429 return CarrierTextController.StatusMode.SimUnknown;
430 }
431
432 private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
433 CharSequence separator) {
434 final boolean plmnValid = !TextUtils.isEmpty(plmn);
435 final boolean spnValid = !TextUtils.isEmpty(spn);
436 if (plmnValid && spnValid) {
437 return new StringBuilder().append(plmn).append(separator).append(spn).toString();
438 } else if (plmnValid) {
439 return plmn;
440 } else if (spnValid) {
441 return spn;
442 } else {
443 return "";
444 }
445 }
446
447 private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
448 if (!TextUtils.isEmpty(string)) {
449 list.add(string);
450 }
451 return list;
452 }
453
454 private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
455 String plmn, String spn) {
456 int carrierHelpTextId = 0;
457 CarrierTextController.StatusMode status = getStatusForIccState(simState);
458 switch (status) {
459 case NetworkLocked:
460 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
461 break;
462
463 case SimMissing:
464 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
465 break;
466
467 case SimPermDisabled:
468 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
469 break;
470
471 case SimMissingLocked:
472 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
473 break;
474
475 case Normal:
476 case SimLocked:
477 case SimPukLocked:
478 break;
479 }
480
481 return mContext.getText(carrierHelpTextId);
482 }
483
484 /**
485 * Callback to communicate to Views
486 */
487 public interface CarrierTextCallback {
488 /**
489 * Provides an updated list of carrier names
490 * @param listOfCarriers
491 * @param simsReady Whether at least one SIM is ready and with service
492 */
493 default void updateCarrierList(CharSequence[] listOfCarriers, boolean simsReady) {};
494
495 /**
496 * Provides an updated full carrier text
497 * @param carrierText
498 * @param simsReady Whether at least one SIM is ready and with service
499 */
500 default void updateCarrierText(CharSequence carrierText, boolean simsReady) {};
501
502 /**
503 * Notifies the View that the device is going to sleep
504 */
505 default void startedGoingToSleep() {};
506
507 /**
508 * Notifies the View that the device finished waking up
509 */
510 default void finishedWakingUp() {};
511 }
512}