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.Context;
20import android.content.res.Resources;
21import android.os.AsyncResult;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.Message;
25import android.preference.CheckBoxPreference;
26import android.preference.Preference;
27import android.preference.PreferenceActivity;
28import android.preference.PreferenceScreen;
29import android.widget.Toast;
30
31import com.android.internal.telephony.Phone;
32import com.android.internal.telephony.PhoneFactory;
33
34/**
35 * Implements the preference screen to enable/disable ICC lock and
36 * also the dialogs to change the ICC PIN. In the former case, enabling/disabling
37 * the ICC lock will prompt the user for the current PIN.
38 * In the Change PIN case, it prompts the user for old pin, new pin and new pin
39 * again before attempting to change it. Calls the SimCard interface to execute
40 * these operations.
41 *
42 */
43public class IccLockSettings extends PreferenceActivity
44        implements EditPinPreference.OnPinEnteredListener {
45
46    private static final int OFF_MODE = 0;
47    // State when enabling/disabling ICC lock
48    private static final int ICC_LOCK_MODE = 1;
49    // State when entering the old pin
50    private static final int ICC_OLD_MODE = 2;
51    // State when entering the new pin - first time
52    private static final int ICC_NEW_MODE = 3;
53    // State when entering the new pin - second time
54    private static final int ICC_REENTER_MODE = 4;
55
56    // Keys in xml file
57    private static final String PIN_DIALOG = "sim_pin";
58    private static final String PIN_TOGGLE = "sim_toggle";
59    // Keys in icicle
60    private static final String DIALOG_STATE = "dialogState";
61    private static final String DIALOG_PIN = "dialogPin";
62    private static final String DIALOG_ERROR = "dialogError";
63    private static final String ENABLE_TO_STATE = "enableState";
64
65    private static final int MIN_PIN_LENGTH = 4;
66    private static final int MAX_PIN_LENGTH = 8;
67    // Which dialog to show next when popped up
68    private int mDialogState = OFF_MODE;
69
70    private String mPin;
71    private String mOldPin;
72    private String mNewPin;
73    private String mError;
74    // Are we trying to enable or disable ICC lock?
75    private boolean mToState;
76
77    private Phone mPhone;
78
79    private EditPinPreference mPinDialog;
80    private CheckBoxPreference mPinToggle;
81
82    private Resources mRes;
83
84    // For async handler to identify request type
85    private static final int ENABLE_ICC_PIN_COMPLETE = 100;
86    private static final int CHANGE_ICC_PIN_COMPLETE = 101;
87
88    // For replies from IccCard interface
89    private Handler mHandler = new Handler() {
90        public void handleMessage(Message msg) {
91            AsyncResult ar = (AsyncResult) msg.obj;
92            switch (msg.what) {
93                case ENABLE_ICC_PIN_COMPLETE:
94                    iccLockChanged(ar.exception == null);
95                    break;
96                case CHANGE_ICC_PIN_COMPLETE:
97                    iccPinChanged(ar.exception == null);
98                    break;
99            }
100
101            return;
102        }
103    };
104
105    // For top-level settings screen to query
106    static boolean isIccLockEnabled() {
107        return PhoneFactory.getDefaultPhone().getIccCard().getIccLockEnabled();
108    }
109
110    static String getSummary(Context context) {
111        Resources res = context.getResources();
112        String summary = isIccLockEnabled()
113                ? res.getString(R.string.sim_lock_on)
114                : res.getString(R.string.sim_lock_off);
115        return summary;
116    }
117
118    @Override
119    protected void onCreate(Bundle savedInstanceState) {
120        super.onCreate(savedInstanceState);
121
122        if (Utils.isMonkeyRunning()) {
123            finish();
124            return;
125        }
126
127        addPreferencesFromResource(R.xml.sim_lock_settings);
128
129        mPinDialog = (EditPinPreference) findPreference(PIN_DIALOG);
130        mPinToggle = (CheckBoxPreference) findPreference(PIN_TOGGLE);
131        if (savedInstanceState != null && savedInstanceState.containsKey(DIALOG_STATE)) {
132            mDialogState = savedInstanceState.getInt(DIALOG_STATE);
133            mPin = savedInstanceState.getString(DIALOG_PIN);
134            mError = savedInstanceState.getString(DIALOG_ERROR);
135            mToState = savedInstanceState.getBoolean(ENABLE_TO_STATE);
136        }
137
138        mPinDialog.setOnPinEnteredListener(this);
139
140        // Don't need any changes to be remembered
141        getPreferenceScreen().setPersistent(false);
142
143        mPhone = PhoneFactory.getDefaultPhone();
144        mRes = getResources();
145    }
146
147    @Override
148    protected void onResume() {
149        super.onResume();
150
151        mPinToggle.setChecked(mPhone.getIccCard().getIccLockEnabled());
152
153        if (mDialogState != OFF_MODE) {
154            showPinDialog();
155        } else {
156            // Prep for standard click on "Change PIN"
157            resetDialogState();
158        }
159    }
160
161    @Override
162    protected void onSaveInstanceState(Bundle out) {
163        // Need to store this state for slider open/close
164        // There is one case where the dialog is popped up by the preference
165        // framework. In that case, let the preference framework store the
166        // dialog state. In other cases, where this activity manually launches
167        // the dialog, store the state of the dialog.
168        if (mPinDialog.isDialogOpen()) {
169            out.putInt(DIALOG_STATE, mDialogState);
170            out.putString(DIALOG_PIN, mPinDialog.getEditText().getText().toString());
171            out.putString(DIALOG_ERROR, mError);
172            out.putBoolean(ENABLE_TO_STATE, mToState);
173        } else {
174            super.onSaveInstanceState(out);
175        }
176    }
177
178    private void showPinDialog() {
179        if (mDialogState == OFF_MODE) {
180            return;
181        }
182        setDialogValues();
183
184        mPinDialog.showPinDialog();
185    }
186
187    private void setDialogValues() {
188        mPinDialog.setText(mPin);
189        String message = "";
190        switch (mDialogState) {
191            case ICC_LOCK_MODE:
192                message = mRes.getString(R.string.sim_enter_pin);
193                mPinDialog.setDialogTitle(mToState
194                        ? mRes.getString(R.string.sim_enable_sim_lock)
195                        : mRes.getString(R.string.sim_disable_sim_lock));
196                break;
197            case ICC_OLD_MODE:
198                message = mRes.getString(R.string.sim_enter_old);
199                mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
200                break;
201            case ICC_NEW_MODE:
202                message = mRes.getString(R.string.sim_enter_new);
203                mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
204                break;
205            case ICC_REENTER_MODE:
206                message = mRes.getString(R.string.sim_reenter_new);
207                mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
208                break;
209        }
210        if (mError != null) {
211            message = mError + "\n" + message;
212            mError = null;
213        }
214        mPinDialog.setDialogMessage(message);
215    }
216
217    public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
218        if (!positiveResult) {
219            resetDialogState();
220            return;
221        }
222
223        mPin = preference.getText();
224        if (!reasonablePin(mPin)) {
225            // inject error message and display dialog again
226            mError = mRes.getString(R.string.sim_bad_pin);
227            showPinDialog();
228            return;
229        }
230        switch (mDialogState) {
231            case ICC_LOCK_MODE:
232                tryChangeIccLockState();
233                break;
234            case ICC_OLD_MODE:
235                mOldPin = mPin;
236                mDialogState = ICC_NEW_MODE;
237                mError = null;
238                mPin = null;
239                showPinDialog();
240                break;
241            case ICC_NEW_MODE:
242                mNewPin = mPin;
243                mDialogState = ICC_REENTER_MODE;
244                mPin = null;
245                showPinDialog();
246                break;
247            case ICC_REENTER_MODE:
248                if (!mPin.equals(mNewPin)) {
249                    mError = mRes.getString(R.string.sim_pins_dont_match);
250                    mDialogState = ICC_NEW_MODE;
251                    mPin = null;
252                    showPinDialog();
253                } else {
254                    mError = null;
255                    tryChangePin();
256                }
257                break;
258        }
259    }
260
261    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
262        if (preference == mPinToggle) {
263            // Get the new, preferred state
264            mToState = mPinToggle.isChecked();
265            // Flip it back and pop up pin dialog
266            mPinToggle.setChecked(!mToState);
267            mDialogState = ICC_LOCK_MODE;
268            showPinDialog();
269        } else if (preference == mPinDialog) {
270            mDialogState = ICC_OLD_MODE;
271            return false;
272        }
273        return true;
274    }
275
276    private void tryChangeIccLockState() {
277        // Try to change icc lock. If it succeeds, toggle the lock state and
278        // reset dialog state. Else inject error message and show dialog again.
279        Message callback = Message.obtain(mHandler, ENABLE_ICC_PIN_COMPLETE);
280        mPhone.getIccCard().setIccLockEnabled(mToState, mPin, callback);
281
282    }
283
284    private void iccLockChanged(boolean success) {
285        if (success) {
286            mPinToggle.setChecked(mToState);
287        } else {
288            Toast.makeText(this, mRes.getString(R.string.sim_lock_failed), Toast.LENGTH_SHORT)
289                    .show();
290        }
291        resetDialogState();
292    }
293
294    private void iccPinChanged(boolean success) {
295        if (!success) {
296            Toast.makeText(this, mRes.getString(R.string.sim_change_failed),
297                    Toast.LENGTH_SHORT)
298                    .show();
299        } else {
300            Toast.makeText(this, mRes.getString(R.string.sim_change_succeeded),
301                    Toast.LENGTH_SHORT)
302                    .show();
303
304        }
305        resetDialogState();
306    }
307
308    private void tryChangePin() {
309        Message callback = Message.obtain(mHandler, CHANGE_ICC_PIN_COMPLETE);
310        mPhone.getIccCard().changeIccLockPassword(mOldPin,
311                mNewPin, callback);
312    }
313
314    private boolean reasonablePin(String pin) {
315        if (pin == null || pin.length() < MIN_PIN_LENGTH || pin.length() > MAX_PIN_LENGTH) {
316            return false;
317        } else {
318            return true;
319        }
320    }
321
322    private void resetDialogState() {
323        mError = null;
324        mDialogState = ICC_OLD_MODE; // Default for when Change PIN is clicked
325        mPin = "";
326        setDialogValues();
327        mDialogState = OFF_MODE;
328    }
329}
330