• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.settings;
18 
19 import android.app.settings.SettingsEnums;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.graphics.PixelFormat;
27 import android.os.AsyncResult;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.telephony.SubscriptionInfo;
32 import android.telephony.SubscriptionManager;
33 import android.telephony.TelephonyManager;
34 import android.text.TextUtils;
35 import android.util.Log;
36 import android.view.Gravity;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.view.WindowManager;
41 import android.widget.EditText;
42 import android.widget.ListView;
43 import android.widget.TabHost;
44 import android.widget.TabHost.OnTabChangeListener;
45 import android.widget.TabHost.TabContentFactory;
46 import android.widget.TabHost.TabSpec;
47 import android.widget.TabWidget;
48 import android.widget.TextView;
49 import android.widget.Toast;
50 
51 import androidx.preference.Preference;
52 import androidx.preference.SwitchPreference;
53 
54 import com.android.internal.telephony.CommandException;
55 import com.android.internal.telephony.Phone;
56 import com.android.internal.telephony.PhoneFactory;
57 import com.android.internal.telephony.TelephonyIntents;
58 
59 /**
60  * Implements the preference screen to enable/disable ICC lock and
61  * also the dialogs to change the ICC PIN. In the former case, enabling/disabling
62  * the ICC lock will prompt the user for the current PIN.
63  * In the Change PIN case, it prompts the user for old pin, new pin and new pin
64  * again before attempting to change it. Calls the SimCard interface to execute
65  * these operations.
66  *
67  */
68 public class IccLockSettings extends SettingsPreferenceFragment
69         implements EditPinPreference.OnPinEnteredListener {
70     private static final String TAG = "IccLockSettings";
71     private static final boolean DBG = false;
72 
73     private static final int OFF_MODE = 0;
74     // State when enabling/disabling ICC lock
75     private static final int ICC_LOCK_MODE = 1;
76     // State when entering the old pin
77     private static final int ICC_OLD_MODE = 2;
78     // State when entering the new pin - first time
79     private static final int ICC_NEW_MODE = 3;
80     // State when entering the new pin - second time
81     private static final int ICC_REENTER_MODE = 4;
82 
83     // Keys in xml file
84     private static final String PIN_DIALOG = "sim_pin";
85     private static final String PIN_TOGGLE = "sim_toggle";
86     // Keys in icicle
87     private static final String DIALOG_STATE = "dialogState";
88     private static final String DIALOG_PIN = "dialogPin";
89     private static final String DIALOG_ERROR = "dialogError";
90     private static final String ENABLE_TO_STATE = "enableState";
91     private static final String CURRENT_TAB = "currentTab";
92 
93     // Save and restore inputted PIN code when configuration changed
94     // (ex. portrait<-->landscape) during change PIN code
95     private static final String OLD_PINCODE = "oldPinCode";
96     private static final String NEW_PINCODE = "newPinCode";
97 
98     private static final int MIN_PIN_LENGTH = 4;
99     private static final int MAX_PIN_LENGTH = 8;
100     // Which dialog to show next when popped up
101     private int mDialogState = OFF_MODE;
102 
103     private String mPin;
104     private String mOldPin;
105     private String mNewPin;
106     private String mError;
107     // Are we trying to enable or disable ICC lock?
108     private boolean mToState;
109 
110     private TabHost mTabHost;
111     private TabWidget mTabWidget;
112     private ListView mListView;
113 
114     private Phone mPhone;
115 
116     private EditPinPreference mPinDialog;
117     private SwitchPreference mPinToggle;
118 
119     private Resources mRes;
120 
121     // For async handler to identify request type
122     private static final int MSG_ENABLE_ICC_PIN_COMPLETE = 100;
123     private static final int MSG_CHANGE_ICC_PIN_COMPLETE = 101;
124     private static final int MSG_SIM_STATE_CHANGED = 102;
125 
126     // @see android.widget.Toast$TN
127     private static final long LONG_DURATION_TIMEOUT = 7000;
128 
129     // For replies from IccCard interface
130     private Handler mHandler = new Handler() {
131         public void handleMessage(Message msg) {
132             AsyncResult ar = (AsyncResult) msg.obj;
133             switch (msg.what) {
134                 case MSG_ENABLE_ICC_PIN_COMPLETE:
135                     iccLockChanged(ar.exception == null, msg.arg1, ar.exception);
136                     break;
137                 case MSG_CHANGE_ICC_PIN_COMPLETE:
138                     iccPinChanged(ar.exception == null, msg.arg1);
139                     break;
140                 case MSG_SIM_STATE_CHANGED:
141                     updatePreferences();
142                     break;
143             }
144 
145             return;
146         }
147     };
148 
149     private final BroadcastReceiver mSimStateReceiver = new BroadcastReceiver() {
150         public void onReceive(Context context, Intent intent) {
151             final String action = intent.getAction();
152             if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
153                 mHandler.sendMessage(mHandler.obtainMessage(MSG_SIM_STATE_CHANGED));
154             }
155         }
156     };
157 
158     // For top-level settings screen to query
isIccLockEnabled()159     static boolean isIccLockEnabled() {
160         return PhoneFactory.getDefaultPhone().getIccCard().getIccLockEnabled();
161     }
162 
getSummary(Context context)163     static String getSummary(Context context) {
164         Resources res = context.getResources();
165         String summary = isIccLockEnabled()
166                 ? res.getString(R.string.sim_lock_on)
167                 : res.getString(R.string.sim_lock_off);
168         return summary;
169     }
170 
171     @Override
onCreate(Bundle savedInstanceState)172     public void onCreate(Bundle savedInstanceState) {
173         super.onCreate(savedInstanceState);
174 
175         if (Utils.isMonkeyRunning()) {
176             finish();
177             return;
178         }
179 
180         addPreferencesFromResource(R.xml.sim_lock_settings);
181 
182         mPinDialog = (EditPinPreference) findPreference(PIN_DIALOG);
183         mPinToggle = (SwitchPreference) findPreference(PIN_TOGGLE);
184         if (savedInstanceState != null && savedInstanceState.containsKey(DIALOG_STATE)) {
185             mDialogState = savedInstanceState.getInt(DIALOG_STATE);
186             mPin = savedInstanceState.getString(DIALOG_PIN);
187             mError = savedInstanceState.getString(DIALOG_ERROR);
188             mToState = savedInstanceState.getBoolean(ENABLE_TO_STATE);
189 
190             // Restore inputted PIN code
191             switch (mDialogState) {
192                 case ICC_NEW_MODE:
193                     mOldPin = savedInstanceState.getString(OLD_PINCODE);
194                     break;
195 
196                 case ICC_REENTER_MODE:
197                     mOldPin = savedInstanceState.getString(OLD_PINCODE);
198                     mNewPin = savedInstanceState.getString(NEW_PINCODE);
199                     break;
200 
201                 case ICC_LOCK_MODE:
202                 case ICC_OLD_MODE:
203                 default:
204                     break;
205             }
206         }
207 
208         mPinDialog.setOnPinEnteredListener(this);
209 
210         // Don't need any changes to be remembered
211         getPreferenceScreen().setPersistent(false);
212 
213         mRes = getResources();
214     }
215 
216     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)217     public View onCreateView(LayoutInflater inflater, ViewGroup container,
218             Bundle savedInstanceState) {
219 
220         final TelephonyManager tm =
221                 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
222         final int numSims = tm.getSimCount();
223         if (numSims > 1) {
224             View view = inflater.inflate(R.layout.icc_lock_tabs, container, false);
225             final ViewGroup prefs_container = (ViewGroup) view.findViewById(R.id.prefs_container);
226             Utils.prepareCustomPreferencesList(container, view, prefs_container, false);
227             View prefs = super.onCreateView(inflater, prefs_container, savedInstanceState);
228             prefs_container.addView(prefs);
229 
230             mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
231             mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
232             mListView = (ListView) view.findViewById(android.R.id.list);
233 
234             mTabHost.setup();
235             mTabHost.setOnTabChangedListener(mTabListener);
236             mTabHost.clearAllTabs();
237 
238             SubscriptionManager sm = SubscriptionManager.from(getContext());
239             for (int i = 0; i < numSims; ++i) {
240                 final SubscriptionInfo subInfo = sm.getActiveSubscriptionInfoForSimSlotIndex(i);
241                 mTabHost.addTab(buildTabSpec(String.valueOf(i),
242                         String.valueOf(subInfo == null
243                             ? getContext().getString(R.string.sim_editor_title, i + 1)
244                             : subInfo.getDisplayName())));
245             }
246             final SubscriptionInfo sir = sm.getActiveSubscriptionInfoForSimSlotIndex(0);
247 
248             mPhone = (sir == null) ? null
249                 : PhoneFactory.getPhone(SubscriptionManager.getPhoneId(sir.getSubscriptionId()));
250 
251             if (savedInstanceState != null && savedInstanceState.containsKey(CURRENT_TAB)) {
252                 mTabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB));
253             }
254             return view;
255         } else {
256             mPhone = PhoneFactory.getDefaultPhone();
257             return super.onCreateView(inflater, container, savedInstanceState);
258         }
259     }
260 
261     @Override
onViewCreated(View view, Bundle savedInstanceState)262     public void onViewCreated(View view, Bundle savedInstanceState) {
263         super.onViewCreated(view, savedInstanceState);
264         updatePreferences();
265     }
266 
updatePreferences()267     private void updatePreferences() {
268         if (mPinDialog != null) {
269             mPinDialog.setEnabled(mPhone != null);
270         }
271         if (mPinToggle != null) {
272             mPinToggle.setEnabled(mPhone != null);
273 
274             if (mPhone != null) {
275                 mPinToggle.setChecked(mPhone.getIccCard().getIccLockEnabled());
276             }
277         }
278     }
279 
280     @Override
getMetricsCategory()281     public int getMetricsCategory() {
282         return SettingsEnums.ICC_LOCK;
283     }
284 
285     @Override
onResume()286     public void onResume() {
287         super.onResume();
288 
289         // ACTION_SIM_STATE_CHANGED is sticky, so we'll receive current state after this call,
290         // which will call updatePreferences().
291         final IntentFilter filter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
292         getContext().registerReceiver(mSimStateReceiver, filter);
293 
294         if (mDialogState != OFF_MODE) {
295             showPinDialog();
296         } else {
297             // Prep for standard click on "Change PIN"
298             resetDialogState();
299         }
300     }
301 
302     @Override
onPause()303     public void onPause() {
304         super.onPause();
305         getContext().unregisterReceiver(mSimStateReceiver);
306     }
307 
308     @Override
getHelpResource()309     public int getHelpResource() {
310         return R.string.help_url_icc_lock;
311     }
312 
313     @Override
onSaveInstanceState(Bundle out)314     public void onSaveInstanceState(Bundle out) {
315         // Need to store this state for slider open/close
316         // There is one case where the dialog is popped up by the preference
317         // framework. In that case, let the preference framework store the
318         // dialog state. In other cases, where this activity manually launches
319         // the dialog, store the state of the dialog.
320         if (mPinDialog.isDialogOpen()) {
321             out.putInt(DIALOG_STATE, mDialogState);
322             out.putString(DIALOG_PIN, mPinDialog.getEditText().getText().toString());
323             out.putString(DIALOG_ERROR, mError);
324             out.putBoolean(ENABLE_TO_STATE, mToState);
325 
326             // Save inputted PIN code
327             switch (mDialogState) {
328                 case ICC_NEW_MODE:
329                     out.putString(OLD_PINCODE, mOldPin);
330                     break;
331 
332                 case ICC_REENTER_MODE:
333                     out.putString(OLD_PINCODE, mOldPin);
334                     out.putString(NEW_PINCODE, mNewPin);
335                     break;
336 
337                 case ICC_LOCK_MODE:
338                 case ICC_OLD_MODE:
339                 default:
340                     break;
341             }
342         } else {
343             super.onSaveInstanceState(out);
344         }
345 
346         if (mTabHost != null) {
347             out.putString(CURRENT_TAB, mTabHost.getCurrentTabTag());
348         }
349     }
350 
showPinDialog()351     private void showPinDialog() {
352         if (mDialogState == OFF_MODE) {
353             return;
354         }
355         setDialogValues();
356 
357         mPinDialog.showPinDialog();
358 
359         final EditText editText = mPinDialog.getEditText();
360         if (!TextUtils.isEmpty(mPin) && editText != null) {
361             editText.setSelection(mPin.length());
362         }
363     }
364 
setDialogValues()365     private void setDialogValues() {
366         mPinDialog.setText(mPin);
367         String message = "";
368         switch (mDialogState) {
369             case ICC_LOCK_MODE:
370                 message = mRes.getString(R.string.sim_enter_pin);
371                 mPinDialog.setDialogTitle(mToState
372                         ? mRes.getString(R.string.sim_enable_sim_lock)
373                         : mRes.getString(R.string.sim_disable_sim_lock));
374                 break;
375             case ICC_OLD_MODE:
376                 message = mRes.getString(R.string.sim_enter_old);
377                 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
378                 break;
379             case ICC_NEW_MODE:
380                 message = mRes.getString(R.string.sim_enter_new);
381                 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
382                 break;
383             case ICC_REENTER_MODE:
384                 message = mRes.getString(R.string.sim_reenter_new);
385                 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
386                 break;
387         }
388         if (mError != null) {
389             message = mError + "\n" + message;
390             mError = null;
391         }
392         mPinDialog.setDialogMessage(message);
393     }
394 
395     @Override
onPinEntered(EditPinPreference preference, boolean positiveResult)396     public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
397         if (!positiveResult) {
398             resetDialogState();
399             return;
400         }
401 
402         mPin = preference.getText();
403         if (!reasonablePin(mPin)) {
404             // inject error message and display dialog again
405             mError = mRes.getString(R.string.sim_bad_pin);
406             showPinDialog();
407             return;
408         }
409         switch (mDialogState) {
410             case ICC_LOCK_MODE:
411                 tryChangeIccLockState();
412                 break;
413             case ICC_OLD_MODE:
414                 mOldPin = mPin;
415                 mDialogState = ICC_NEW_MODE;
416                 mError = null;
417                 mPin = null;
418                 showPinDialog();
419                 break;
420             case ICC_NEW_MODE:
421                 mNewPin = mPin;
422                 mDialogState = ICC_REENTER_MODE;
423                 mPin = null;
424                 showPinDialog();
425                 break;
426             case ICC_REENTER_MODE:
427                 if (!mPin.equals(mNewPin)) {
428                     mError = mRes.getString(R.string.sim_pins_dont_match);
429                     mDialogState = ICC_NEW_MODE;
430                     mPin = null;
431                     showPinDialog();
432                 } else {
433                     mError = null;
434                     tryChangePin();
435                 }
436                 break;
437         }
438     }
439 
440     @Override
onPreferenceTreeClick(Preference preference)441     public boolean onPreferenceTreeClick(Preference preference) {
442         if (preference == mPinToggle) {
443             // Get the new, preferred state
444             mToState = mPinToggle.isChecked();
445             // Flip it back and pop up pin dialog
446             mPinToggle.setChecked(!mToState);
447             mDialogState = ICC_LOCK_MODE;
448             showPinDialog();
449         } else if (preference == mPinDialog) {
450             mDialogState = ICC_OLD_MODE;
451             return false;
452         }
453         return true;
454     }
455 
tryChangeIccLockState()456     private void tryChangeIccLockState() {
457         // Try to change icc lock. If it succeeds, toggle the lock state and
458         // reset dialog state. Else inject error message and show dialog again.
459         Message callback = Message.obtain(mHandler, MSG_ENABLE_ICC_PIN_COMPLETE);
460         mPhone.getIccCard().setIccLockEnabled(mToState, mPin, callback);
461         // Disable the setting till the response is received.
462         mPinToggle.setEnabled(false);
463     }
464 
iccLockChanged(boolean success, int attemptsRemaining, Throwable exception)465     private void iccLockChanged(boolean success, int attemptsRemaining, Throwable exception) {
466         if (success) {
467             mPinToggle.setChecked(mToState);
468         } else {
469             if (exception instanceof CommandException) {
470                 CommandException.Error err = ((CommandException)(exception)).getCommandError();
471                 if (err == CommandException.Error.PASSWORD_INCORRECT) {
472                     createCustomTextToast(getPinPasswordErrorMessage(attemptsRemaining));
473                 } else {
474                     if (mToState) {
475                         Toast.makeText(getContext(), mRes.getString
476                                (R.string.sim_pin_enable_failed), Toast.LENGTH_LONG).show();
477                     } else {
478                         Toast.makeText(getContext(), mRes.getString
479                                (R.string.sim_pin_disable_failed), Toast.LENGTH_LONG).show();
480                     }
481                 }
482             }
483         }
484         mPinToggle.setEnabled(true);
485         resetDialogState();
486     }
487 
createCustomTextToast(CharSequence errorMessage)488     private void createCustomTextToast(CharSequence errorMessage) {
489         // Cannot overlay Toast on PUK unlock screen.
490         // The window type of Toast is set by NotificationManagerService.
491         // It can't be overwritten by LayoutParams.type.
492         // Ovarlay a custom window with LayoutParams (TYPE_STATUS_BAR_PANEL) on PUK unlock screen.
493         View v = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE))
494                 .inflate(com.android.internal.R.layout.transient_notification, null);
495         TextView tv = (TextView) v.findViewById(com.android.internal.R.id.message);
496         tv.setText(errorMessage);
497 
498         final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
499         final Configuration config = v.getContext().getResources().getConfiguration();
500         final int gravity = Gravity.getAbsoluteGravity(
501                 getContext().getResources().getInteger(
502                         com.android.internal.R.integer.config_toastDefaultGravity),
503                 config.getLayoutDirection());
504         params.gravity = gravity;
505         if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
506             params.horizontalWeight = 1.0f;
507         }
508         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
509             params.verticalWeight = 1.0f;
510         }
511         params.y = getContext().getResources().getDimensionPixelSize(
512                 com.android.internal.R.dimen.toast_y_offset);
513 
514         params.height = WindowManager.LayoutParams.WRAP_CONTENT;
515         params.width = WindowManager.LayoutParams.WRAP_CONTENT;
516         params.format = PixelFormat.TRANSLUCENT;
517         params.windowAnimations = com.android.internal.R.style.Animation_Toast;
518         params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
519         params.setTitle(errorMessage);
520         params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
521                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
522                 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
523 
524         WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
525         wm.addView(v, params);
526 
527         mHandler.postDelayed(new Runnable() {
528             @Override
529             public void run() {
530                 wm.removeViewImmediate(v);
531             }
532         }, LONG_DURATION_TIMEOUT);
533     }
534 
iccPinChanged(boolean success, int attemptsRemaining)535     private void iccPinChanged(boolean success, int attemptsRemaining) {
536         if (!success) {
537             createCustomTextToast(getPinPasswordErrorMessage(attemptsRemaining));
538         } else {
539             Toast.makeText(getContext(), mRes.getString(R.string.sim_change_succeeded),
540                     Toast.LENGTH_SHORT)
541                     .show();
542 
543         }
544         resetDialogState();
545     }
546 
tryChangePin()547     private void tryChangePin() {
548         Message callback = Message.obtain(mHandler, MSG_CHANGE_ICC_PIN_COMPLETE);
549         mPhone.getIccCard().changeIccLockPassword(mOldPin,
550                 mNewPin, callback);
551     }
552 
getPinPasswordErrorMessage(int attemptsRemaining)553     private String getPinPasswordErrorMessage(int attemptsRemaining) {
554         String displayMessage;
555 
556         if (attemptsRemaining == 0) {
557             displayMessage = mRes.getString(R.string.wrong_pin_code_pukked);
558         } else if (attemptsRemaining > 0) {
559             displayMessage = mRes
560                     .getQuantityString(R.plurals.wrong_pin_code, attemptsRemaining,
561                             attemptsRemaining);
562         } else {
563             displayMessage = mRes.getString(R.string.pin_failed);
564         }
565         if (DBG) Log.d(TAG, "getPinPasswordErrorMessage:"
566                 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
567         return displayMessage;
568     }
569 
reasonablePin(String pin)570     private boolean reasonablePin(String pin) {
571         if (pin == null || pin.length() < MIN_PIN_LENGTH || pin.length() > MAX_PIN_LENGTH) {
572             return false;
573         } else {
574             return true;
575         }
576     }
577 
resetDialogState()578     private void resetDialogState() {
579         mError = null;
580         mDialogState = ICC_OLD_MODE; // Default for when Change PIN is clicked
581         mPin = "";
582         setDialogValues();
583         mDialogState = OFF_MODE;
584     }
585 
586     private OnTabChangeListener mTabListener = new OnTabChangeListener() {
587         @Override
588         public void onTabChanged(String tabId) {
589             final int slotId = Integer.parseInt(tabId);
590             final SubscriptionInfo sir = SubscriptionManager.from(getActivity().getBaseContext())
591                     .getActiveSubscriptionInfoForSimSlotIndex(slotId);
592 
593             mPhone = (sir == null) ? null
594                 : PhoneFactory.getPhone(SubscriptionManager.getPhoneId(sir.getSubscriptionId()));
595 
596             // The User has changed tab; update the body.
597             updatePreferences();
598         }
599     };
600 
601     private TabContentFactory mEmptyTabContent = new TabContentFactory() {
602         @Override
603         public View createTabContent(String tag) {
604             return new View(mTabHost.getContext());
605         }
606     };
607 
buildTabSpec(String tag, String title)608     private TabSpec buildTabSpec(String tag, String title) {
609         return mTabHost.newTabSpec(tag).setIndicator(title).setContent(
610                 mEmptyTabContent);
611     }
612 }
613