• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 
17 package com.android.keyguard;
18 
19 import java.util.List;
20 import java.util.Locale;
21 import java.util.Objects;
22 
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.res.TypedArray;
27 import android.net.ConnectivityManager;
28 import android.net.wifi.WifiManager;
29 import android.telephony.ServiceState;
30 import android.telephony.SubscriptionInfo;
31 import android.text.TextUtils;
32 import android.text.method.SingleLineTransformationMethod;
33 import android.util.AttributeSet;
34 import android.util.Log;
35 import android.view.View;
36 import android.widget.TextView;
37 
38 import com.android.internal.telephony.IccCardConstants;
39 import com.android.internal.telephony.IccCardConstants.State;
40 import com.android.internal.telephony.TelephonyIntents;
41 import com.android.settingslib.WirelessUtils;
42 
43 import android.telephony.TelephonyManager;
44 
45 public class CarrierText extends TextView {
46     /** Do not show missing sim message. */
47     public static final int FLAG_HIDE_MISSING_SIM = 1 << 0;
48     /** Do not show airplane mode message. */
49     public static final int FLAG_HIDE_AIRPLANE_MODE = 1 << 1;
50 
51     private static final boolean DEBUG = KeyguardConstants.DEBUG;
52     private static final String TAG = "CarrierText";
53 
54     private static CharSequence mSeparator;
55 
56     private final boolean mIsEmergencyCallCapable;
57 
58     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
59 
60     private WifiManager mWifiManager;
61 
62     private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
63 
64     private int mFlags;
65 
66     private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
67         @Override
68         public void onRefreshCarrierInfo() {
69             updateCarrierText();
70         }
71 
72         public void onFinishedGoingToSleep(int why) {
73             setSelected(false);
74         };
75 
76         public void onStartedWakingUp() {
77             setSelected(true);
78         };
79 
80         public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
81             if (slotId < 0) {
82                 Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId);
83                 return;
84             }
85 
86             if (DEBUG) Log.d(TAG,"onSimStateChanged: " + getStatusForIccState(simState));
87             if (getStatusForIccState(simState) == StatusMode.SimIoError) {
88                 mSimErrorState[slotId] = true;
89                 updateCarrierText();
90             } else if (mSimErrorState[slotId]) {
91                 mSimErrorState[slotId] = false;
92                 updateCarrierText();
93             }
94         };
95     };
96 
setDisplayFlags(int flags)97     public void setDisplayFlags(int flags) {
98         mFlags = flags;
99     }
100 
101     /**
102      * The status of this lock screen. Primarily used for widgets on LockScreen.
103      */
104     private static enum StatusMode {
105         Normal, // Normal case (sim card present, it's not locked)
106         NetworkLocked, // SIM card is 'network locked'.
107         SimMissing, // SIM card is missing.
108         SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
109         SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
110         SimLocked, // SIM card is currently locked
111         SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
112         SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
113         SimIoError; // SIM card is faulty
114     }
115 
CarrierText(Context context)116     public CarrierText(Context context) {
117         this(context, null);
118     }
119 
CarrierText(Context context, AttributeSet attrs)120     public CarrierText(Context context, AttributeSet attrs) {
121         super(context, attrs);
122         mIsEmergencyCallCapable = context.getResources().getBoolean(
123                 com.android.internal.R.bool.config_voice_capable);
124         boolean useAllCaps;
125         TypedArray a = context.getTheme().obtainStyledAttributes(
126                 attrs, R.styleable.CarrierText, 0, 0);
127         try {
128             useAllCaps = a.getBoolean(R.styleable.CarrierText_allCaps, false);
129         } finally {
130             a.recycle();
131         }
132         setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
133 
134         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
135     }
136 
137     /**
138      * Checks if there are faulty cards. Adds the text depending on the slot of the card
139      * @param text: current carrier text based on the sim state
140      * @param noSims: whether a valid sim card is inserted
141      * @return text
142     */
updateCarrierTextWithSimIoError(CharSequence text, boolean noSims)143     private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) {
144         final CharSequence carrier = "";
145         CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
146             IccCardConstants.State.CARD_IO_ERROR, carrier);
147         for (int index = 0; index < mSimErrorState.length; index++) {
148             if (mSimErrorState[index]) {
149                 // In the case when no sim cards are detected but a faulty card is inserted
150                 // overwrite the text and only show "Invalid card"
151                 if (noSims) {
152                     return concatenate(carrierTextForSimIOError,
153                         getContext().getText(com.android.internal.R.string.emergency_calls_only));
154                 } else if (index == 0) {
155                     // prepend "Invalid card" when faulty card is inserted in slot 0
156                     text = concatenate(carrierTextForSimIOError, text);
157                 } else {
158                     // concatenate "Invalid card" when faulty card is inserted in slot 1
159                     text = concatenate(text, carrierTextForSimIOError);
160                 }
161             }
162         }
163         return text;
164     }
165 
updateCarrierText()166     protected void updateCarrierText() {
167         boolean allSimsMissing = true;
168         boolean anySimReadyAndInService = false;
169         CharSequence displayText = null;
170 
171         List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
172         final int N = subs.size();
173         if (DEBUG) Log.d(TAG, "updateCarrierText(): " + N);
174         for (int i = 0; i < N; i++) {
175             int subId = subs.get(i).getSubscriptionId();
176             State simState = mKeyguardUpdateMonitor.getSimState(subId);
177             CharSequence carrierName = subs.get(i).getCarrierName();
178             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
179             if (DEBUG) {
180                 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
181             }
182             if (carrierTextForSimState != null) {
183                 allSimsMissing = false;
184                 displayText = concatenate(displayText, carrierTextForSimState);
185             }
186             if (simState == IccCardConstants.State.READY) {
187                 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
188                 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
189                     // hack for WFC (IWLAN) not turning off immediately once
190                     // Wi-Fi is disassociated or disabled
191                     if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
192                             || (mWifiManager.isWifiEnabled()
193                                     && mWifiManager.getConnectionInfo() != null
194                                     && mWifiManager.getConnectionInfo().getBSSID() != null)) {
195                         if (DEBUG) {
196                             Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
197                         }
198                         anySimReadyAndInService = true;
199                     }
200                 }
201             }
202         }
203         if (allSimsMissing) {
204             if (N != 0) {
205                 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
206                 // This depends on mPlmn containing the text "Emergency calls only" when the radio
207                 // has some connectivity. Otherwise, it should be null or empty and just show
208                 // "No SIM card"
209                 // Grab the first subscripton, because they all should contain the emergency text,
210                 // described above.
211                 displayText =  makeCarrierStringOnEmergencyCapable(
212                         getMissingSimMessage(), subs.get(0).getCarrierName());
213             } else {
214                 // We don't have a SubscriptionInfo to get the emergency calls only from.
215                 // Grab it from the old sticky broadcast if possible instead. We can use it
216                 // here because no subscriptions are active, so we don't have
217                 // to worry about MSIM clashing.
218                 CharSequence text =
219                         getContext().getText(com.android.internal.R.string.emergency_calls_only);
220                 Intent i = getContext().registerReceiver(null,
221                         new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
222                 if (i != null) {
223                     String spn = "";
224                     String plmn = "";
225                     if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
226                         spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
227                     }
228                     if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
229                         plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
230                     }
231                     if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
232                     if (Objects.equals(plmn, spn)) {
233                         text = plmn;
234                     } else {
235                         text = concatenate(plmn, spn);
236                     }
237                 }
238                 displayText =  makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
239             }
240         }
241 
242         displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing);
243         // APM (airplane mode) != no carrier state. There are carrier services
244         // (e.g. WFC = Wi-Fi calling) which may operate in APM.
245         if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
246             displayText = getAirplaneModeMessage();
247         }
248         setText(displayText);
249     }
250 
getMissingSimMessage()251     private String getMissingSimMessage() {
252         return (mFlags & FLAG_HIDE_MISSING_SIM) == 0
253                 ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
254     }
255 
getAirplaneModeMessage()256     private String getAirplaneModeMessage() {
257         return (mFlags & FLAG_HIDE_AIRPLANE_MODE) == 0
258                 ? getContext().getString(R.string.airplane_mode) : "";
259     }
260 
261     @Override
onFinishInflate()262     protected void onFinishInflate() {
263         super.onFinishInflate();
264         mSeparator = getResources().getString(
265                 com.android.internal.R.string.kg_text_message_separator);
266         boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
267         setSelected(shouldMarquee); // Allow marquee to work.
268     }
269 
270     @Override
onAttachedToWindow()271     protected void onAttachedToWindow() {
272         super.onAttachedToWindow();
273         if (ConnectivityManager.from(mContext).isNetworkSupported(
274                 ConnectivityManager.TYPE_MOBILE)) {
275             mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
276             mKeyguardUpdateMonitor.registerCallback(mCallback);
277         } else {
278             // Don't listen and clear out the text when the device isn't a phone.
279             mKeyguardUpdateMonitor = null;
280             setText("");
281         }
282     }
283 
284     @Override
onDetachedFromWindow()285     protected void onDetachedFromWindow() {
286         super.onDetachedFromWindow();
287         if (mKeyguardUpdateMonitor != null) {
288             mKeyguardUpdateMonitor.removeCallback(mCallback);
289         }
290     }
291 
292     @Override
onVisibilityChanged(View changedView, int visibility)293     protected void onVisibilityChanged(View changedView, int visibility) {
294         super.onVisibilityChanged(changedView, visibility);
295 
296         // Only show marquee when visible
297         if (visibility == VISIBLE) {
298             setEllipsize(TextUtils.TruncateAt.MARQUEE);
299         } else {
300             setEllipsize(TextUtils.TruncateAt.END);
301         }
302     }
303 
304     /**
305      * Top-level function for creating carrier text. Makes text based on simState, PLMN
306      * and SPN as well as device capabilities, such as being emergency call capable.
307      *
308      * @param simState
309      * @param text
310      * @param spn
311      * @return Carrier text if not in missing state, null otherwise.
312      */
getCarrierTextForSimState(IccCardConstants.State simState, CharSequence text)313     private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
314             CharSequence text) {
315         CharSequence carrierText = null;
316         StatusMode status = getStatusForIccState(simState);
317         switch (status) {
318             case Normal:
319                 carrierText = text;
320                 break;
321 
322             case SimNotReady:
323                 // Null is reserved for denoting missing, in this case we have nothing to display.
324                 carrierText = ""; // nothing to display yet.
325                 break;
326 
327             case NetworkLocked:
328                 carrierText = makeCarrierStringOnEmergencyCapable(
329                         mContext.getText(R.string.keyguard_network_locked_message), text);
330                 break;
331 
332             case SimMissing:
333                 carrierText = null;
334                 break;
335 
336             case SimPermDisabled:
337                 carrierText = makeCarrierStringOnEmergencyCapable(
338                         getContext().getText(
339                                 R.string.keyguard_permanent_disabled_sim_message_short),
340                         text);
341                 break;
342 
343             case SimMissingLocked:
344                 carrierText = null;
345                 break;
346 
347             case SimLocked:
348                 carrierText = makeCarrierStringOnEmergencyCapable(
349                         getContext().getText(R.string.keyguard_sim_locked_message),
350                         text);
351                 break;
352 
353             case SimPukLocked:
354                 carrierText = makeCarrierStringOnEmergencyCapable(
355                         getContext().getText(R.string.keyguard_sim_puk_locked_message),
356                         text);
357                 break;
358             case SimIoError:
359                 carrierText = makeCarrierStringOnEmergencyCapable(
360                         getContext().getText(R.string.keyguard_sim_error_message_short),
361                         text);
362                 break;
363         }
364 
365         return carrierText;
366     }
367 
368     /*
369      * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
370      */
makeCarrierStringOnEmergencyCapable( CharSequence simMessage, CharSequence emergencyCallMessage)371     private CharSequence makeCarrierStringOnEmergencyCapable(
372             CharSequence simMessage, CharSequence emergencyCallMessage) {
373         if (mIsEmergencyCallCapable) {
374             return concatenate(simMessage, emergencyCallMessage);
375         }
376         return simMessage;
377     }
378 
379     /**
380      * Determine the current status of the lock screen given the SIM state and other stuff.
381      */
getStatusForIccState(IccCardConstants.State simState)382     private StatusMode getStatusForIccState(IccCardConstants.State simState) {
383         // Since reading the SIM may take a while, we assume it is present until told otherwise.
384         if (simState == null) {
385             return StatusMode.Normal;
386         }
387 
388         final boolean missingAndNotProvisioned =
389                 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
390                 && (simState == IccCardConstants.State.ABSENT ||
391                         simState == IccCardConstants.State.PERM_DISABLED);
392 
393         // Assume we're NETWORK_LOCKED if not provisioned
394         simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
395         switch (simState) {
396             case ABSENT:
397                 return StatusMode.SimMissing;
398             case NETWORK_LOCKED:
399                 return StatusMode.SimMissingLocked;
400             case NOT_READY:
401                 return StatusMode.SimNotReady;
402             case PIN_REQUIRED:
403                 return StatusMode.SimLocked;
404             case PUK_REQUIRED:
405                 return StatusMode.SimPukLocked;
406             case READY:
407                 return StatusMode.Normal;
408             case PERM_DISABLED:
409                 return StatusMode.SimPermDisabled;
410             case UNKNOWN:
411                 return StatusMode.SimMissing;
412             case CARD_IO_ERROR:
413                 return StatusMode.SimIoError;
414         }
415         return StatusMode.SimMissing;
416     }
417 
concatenate(CharSequence plmn, CharSequence spn)418     private static CharSequence concatenate(CharSequence plmn, CharSequence spn) {
419         final boolean plmnValid = !TextUtils.isEmpty(plmn);
420         final boolean spnValid = !TextUtils.isEmpty(spn);
421         if (plmnValid && spnValid) {
422             return new StringBuilder().append(plmn).append(mSeparator).append(spn).toString();
423         } else if (plmnValid) {
424             return plmn;
425         } else if (spnValid) {
426             return spn;
427         } else {
428             return "";
429         }
430     }
431 
getCarrierHelpTextForSimState(IccCardConstants.State simState, String plmn, String spn)432     private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
433             String plmn, String spn) {
434         int carrierHelpTextId = 0;
435         StatusMode status = getStatusForIccState(simState);
436         switch (status) {
437             case NetworkLocked:
438                 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
439                 break;
440 
441             case SimMissing:
442                 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
443                 break;
444 
445             case SimPermDisabled:
446                 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
447                 break;
448 
449             case SimMissingLocked:
450                 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
451                 break;
452 
453             case Normal:
454             case SimLocked:
455             case SimPukLocked:
456                 break;
457         }
458 
459         return mContext.getText(carrierHelpTextId);
460     }
461 
462     private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
463         private final Locale mLocale;
464         private final boolean mAllCaps;
465 
CarrierTextTransformationMethod(Context context, boolean allCaps)466         public CarrierTextTransformationMethod(Context context, boolean allCaps) {
467             mLocale = context.getResources().getConfiguration().locale;
468             mAllCaps = allCaps;
469         }
470 
471         @Override
getTransformation(CharSequence source, View view)472         public CharSequence getTransformation(CharSequence source, View view) {
473             source = super.getTransformation(source, view);
474 
475             if (mAllCaps && source != null) {
476                 source = source.toString().toUpperCase(mLocale);
477             }
478 
479             return source;
480         }
481     }
482 }
483