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