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