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 public class CarrierText extends TextView {
44     private static final boolean DEBUG = KeyguardConstants.DEBUG;
45     private static final String TAG = "CarrierText";
46 
47     private static CharSequence mSeparator;
48 
49     private final boolean mIsEmergencyCallCapable;
50 
51     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
52 
53     private WifiManager mWifiManager;
54 
55     private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
56         @Override
57         public void onRefreshCarrierInfo() {
58             updateCarrierText();
59         }
60 
61         public void onFinishedGoingToSleep(int why) {
62             setSelected(false);
63         };
64 
65         public void onStartedWakingUp() {
66             setSelected(true);
67         };
68     };
69     /**
70      * The status of this lock screen. Primarily used for widgets on LockScreen.
71      */
72     private static enum StatusMode {
73         Normal, // Normal case (sim card present, it's not locked)
74         NetworkLocked, // SIM card is 'network locked'.
75         SimMissing, // SIM card is missing.
76         SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
77         SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
78         SimLocked, // SIM card is currently locked
79         SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
80         SimNotReady; // SIM is not ready yet. May never be on devices w/o a SIM.
81     }
82 
CarrierText(Context context)83     public CarrierText(Context context) {
84         this(context, null);
85     }
86 
CarrierText(Context context, AttributeSet attrs)87     public CarrierText(Context context, AttributeSet attrs) {
88         super(context, attrs);
89         mIsEmergencyCallCapable = context.getResources().getBoolean(
90                 com.android.internal.R.bool.config_voice_capable);
91         boolean useAllCaps;
92         TypedArray a = context.getTheme().obtainStyledAttributes(
93                 attrs, R.styleable.CarrierText, 0, 0);
94         try {
95             useAllCaps = a.getBoolean(R.styleable.CarrierText_allCaps, false);
96         } finally {
97             a.recycle();
98         }
99         setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
100 
101         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
102     }
103 
updateCarrierText()104     protected void updateCarrierText() {
105         boolean allSimsMissing = true;
106         boolean anySimReadyAndInService = false;
107         CharSequence displayText = null;
108 
109         List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
110         final int N = subs.size();
111         if (DEBUG) Log.d(TAG, "updateCarrierText(): " + N);
112         for (int i = 0; i < N; i++) {
113             int subId = subs.get(i).getSubscriptionId();
114             State simState = mKeyguardUpdateMonitor.getSimState(subId);
115             CharSequence carrierName = subs.get(i).getCarrierName();
116             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
117             if (DEBUG) {
118                 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
119             }
120             if (carrierTextForSimState != null) {
121                 allSimsMissing = false;
122                 displayText = concatenate(displayText, carrierTextForSimState);
123             }
124             if (simState == IccCardConstants.State.READY) {
125                 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
126                 if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
127                     // hack for WFC (IWLAN) not turning off immediately once
128                     // Wi-Fi is disassociated or disabled
129                     if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
130                             || (mWifiManager.isWifiEnabled()
131                                     && mWifiManager.getConnectionInfo() != null
132                                     && mWifiManager.getConnectionInfo().getBSSID() != null)) {
133                         if (DEBUG) {
134                             Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
135                         }
136                         anySimReadyAndInService = true;
137                     }
138                 }
139             }
140         }
141         if (allSimsMissing) {
142             if (N != 0) {
143                 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
144                 // This depends on mPlmn containing the text "Emergency calls only" when the radio
145                 // has some connectivity. Otherwise, it should be null or empty and just show
146                 // "No SIM card"
147                 // Grab the first subscripton, because they all should contain the emergency text,
148                 // described above.
149                 displayText =  makeCarrierStringOnEmergencyCapable(
150                         getContext().getText(R.string.keyguard_missing_sim_message_short),
151                         subs.get(0).getCarrierName());
152             } else {
153                 // We don't have a SubscriptionInfo to get the emergency calls only from.
154                 // Grab it from the old sticky broadcast if possible instead. We can use it
155                 // here because no subscriptions are active, so we don't have
156                 // to worry about MSIM clashing.
157                 CharSequence text =
158                         getContext().getText(com.android.internal.R.string.emergency_calls_only);
159                 Intent i = getContext().registerReceiver(null,
160                         new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
161                 if (i != null) {
162                     String spn = "";
163                     String plmn = "";
164                     if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
165                         spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
166                     }
167                     if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
168                         plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
169                     }
170                     if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
171                     if (Objects.equals(plmn, spn)) {
172                         text = plmn;
173                     } else {
174                         text = concatenate(plmn, spn);
175                     }
176                 }
177                 displayText =  makeCarrierStringOnEmergencyCapable(
178                         getContext().getText(R.string.keyguard_missing_sim_message_short), text);
179             }
180         }
181 
182         // APM (airplane mode) != no carrier state. There are carrier services
183         // (e.g. WFC = Wi-Fi calling) which may operate in APM.
184         if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
185             displayText = getContext().getString(R.string.airplane_mode);
186         }
187         setText(displayText);
188     }
189 
190     @Override
onFinishInflate()191     protected void onFinishInflate() {
192         super.onFinishInflate();
193         mSeparator = getResources().getString(
194                 com.android.internal.R.string.kg_text_message_separator);
195         boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
196         setSelected(shouldMarquee); // Allow marquee to work.
197     }
198 
199     @Override
onAttachedToWindow()200     protected void onAttachedToWindow() {
201         super.onAttachedToWindow();
202         if (ConnectivityManager.from(mContext).isNetworkSupported(
203                 ConnectivityManager.TYPE_MOBILE)) {
204             mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
205             mKeyguardUpdateMonitor.registerCallback(mCallback);
206         } else {
207             // Don't listen and clear out the text when the device isn't a phone.
208             mKeyguardUpdateMonitor = null;
209             setText("");
210         }
211     }
212 
213     @Override
onDetachedFromWindow()214     protected void onDetachedFromWindow() {
215         super.onDetachedFromWindow();
216         if (mKeyguardUpdateMonitor != null) {
217             mKeyguardUpdateMonitor.removeCallback(mCallback);
218         }
219     }
220 
221     /**
222      * Top-level function for creating carrier text. Makes text based on simState, PLMN
223      * and SPN as well as device capabilities, such as being emergency call capable.
224      *
225      * @param simState
226      * @param text
227      * @param spn
228      * @return Carrier text if not in missing state, null otherwise.
229      */
getCarrierTextForSimState(IccCardConstants.State simState, CharSequence text)230     private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
231             CharSequence text) {
232         CharSequence carrierText = null;
233         StatusMode status = getStatusForIccState(simState);
234         switch (status) {
235             case Normal:
236                 carrierText = text;
237                 break;
238 
239             case SimNotReady:
240                 // Null is reserved for denoting missing, in this case we have nothing to display.
241                 carrierText = ""; // nothing to display yet.
242                 break;
243 
244             case NetworkLocked:
245                 carrierText = makeCarrierStringOnEmergencyCapable(
246                         mContext.getText(R.string.keyguard_network_locked_message), text);
247                 break;
248 
249             case SimMissing:
250                 carrierText = null;
251                 break;
252 
253             case SimPermDisabled:
254                 carrierText = getContext().getText(
255                         R.string.keyguard_permanent_disabled_sim_message_short);
256                 break;
257 
258             case SimMissingLocked:
259                 carrierText = null;
260                 break;
261 
262             case SimLocked:
263                 carrierText = makeCarrierStringOnEmergencyCapable(
264                         getContext().getText(R.string.keyguard_sim_locked_message),
265                         text);
266                 break;
267 
268             case SimPukLocked:
269                 carrierText = makeCarrierStringOnEmergencyCapable(
270                         getContext().getText(R.string.keyguard_sim_puk_locked_message),
271                         text);
272                 break;
273         }
274 
275         return carrierText;
276     }
277 
278     /*
279      * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
280      */
makeCarrierStringOnEmergencyCapable( CharSequence simMessage, CharSequence emergencyCallMessage)281     private CharSequence makeCarrierStringOnEmergencyCapable(
282             CharSequence simMessage, CharSequence emergencyCallMessage) {
283         if (mIsEmergencyCallCapable) {
284             return concatenate(simMessage, emergencyCallMessage);
285         }
286         return simMessage;
287     }
288 
289     /**
290      * Determine the current status of the lock screen given the SIM state and other stuff.
291      */
getStatusForIccState(IccCardConstants.State simState)292     private StatusMode getStatusForIccState(IccCardConstants.State simState) {
293         // Since reading the SIM may take a while, we assume it is present until told otherwise.
294         if (simState == null) {
295             return StatusMode.Normal;
296         }
297 
298         final boolean missingAndNotProvisioned =
299                 !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
300                 && (simState == IccCardConstants.State.ABSENT ||
301                         simState == IccCardConstants.State.PERM_DISABLED);
302 
303         // Assume we're NETWORK_LOCKED if not provisioned
304         simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
305         switch (simState) {
306             case ABSENT:
307                 return StatusMode.SimMissing;
308             case NETWORK_LOCKED:
309                 return StatusMode.SimMissingLocked;
310             case NOT_READY:
311                 return StatusMode.SimNotReady;
312             case PIN_REQUIRED:
313                 return StatusMode.SimLocked;
314             case PUK_REQUIRED:
315                 return StatusMode.SimPukLocked;
316             case READY:
317                 return StatusMode.Normal;
318             case PERM_DISABLED:
319                 return StatusMode.SimPermDisabled;
320             case UNKNOWN:
321                 return StatusMode.SimMissing;
322         }
323         return StatusMode.SimMissing;
324     }
325 
concatenate(CharSequence plmn, CharSequence spn)326     private static CharSequence concatenate(CharSequence plmn, CharSequence spn) {
327         final boolean plmnValid = !TextUtils.isEmpty(plmn);
328         final boolean spnValid = !TextUtils.isEmpty(spn);
329         if (plmnValid && spnValid) {
330             return new StringBuilder().append(plmn).append(mSeparator).append(spn).toString();
331         } else if (plmnValid) {
332             return plmn;
333         } else if (spnValid) {
334             return spn;
335         } else {
336             return "";
337         }
338     }
339 
getCarrierHelpTextForSimState(IccCardConstants.State simState, String plmn, String spn)340     private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
341             String plmn, String spn) {
342         int carrierHelpTextId = 0;
343         StatusMode status = getStatusForIccState(simState);
344         switch (status) {
345             case NetworkLocked:
346                 carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
347                 break;
348 
349             case SimMissing:
350                 carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
351                 break;
352 
353             case SimPermDisabled:
354                 carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
355                 break;
356 
357             case SimMissingLocked:
358                 carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
359                 break;
360 
361             case Normal:
362             case SimLocked:
363             case SimPukLocked:
364                 break;
365         }
366 
367         return mContext.getText(carrierHelpTextId);
368     }
369 
370     private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
371         private final Locale mLocale;
372         private final boolean mAllCaps;
373 
CarrierTextTransformationMethod(Context context, boolean allCaps)374         public CarrierTextTransformationMethod(Context context, boolean allCaps) {
375             mLocale = context.getResources().getConfiguration().locale;
376             mAllCaps = allCaps;
377         }
378 
379         @Override
getTransformation(CharSequence source, View view)380         public CharSequence getTransformation(CharSequence source, View view) {
381             source = super.getTransformation(source, view);
382 
383             if (mAllCaps && source != null) {
384                 source = source.toString().toUpperCase(mLocale);
385             }
386 
387             return source;
388         }
389     }
390 }
391