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