FdnSetting.java revision 83383e4c2f748514124eaa0482091490c4c8a311
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.phone.settings.fdn;
18
19import android.app.ActionBar;
20import android.app.AlertDialog;
21import android.content.DialogInterface;
22import android.os.AsyncResult;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.Message;
26import android.util.Log;
27import android.preference.PreferenceActivity;
28import android.preference.PreferenceScreen;
29import android.view.MenuItem;
30import android.view.WindowManager;
31import android.widget.Toast;
32
33import com.android.internal.telephony.CommandException;
34import com.android.internal.telephony.Phone;
35import com.android.phone.CallFeaturesSetting;
36import com.android.phone.PhoneGlobals;
37import com.android.phone.R;
38
39/**
40 * FDN settings UI for the Phone app.
41 * Rewritten to look and behave closer to the other preferences.
42 */
43public class FdnSetting extends PreferenceActivity
44        implements EditPinPreference.OnPinEnteredListener, DialogInterface.OnCancelListener {
45
46    private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
47    private static final boolean DBG = false;
48
49    private Phone mPhone;
50
51    /**
52     * Events we handle.
53     * The first is used for toggling FDN enable, the second for the PIN change.
54     */
55    private static final int EVENT_PIN2_ENTRY_COMPLETE = 100;
56    private static final int EVENT_PIN2_CHANGE_COMPLETE = 200;
57
58    // String keys for preference lookup
59    // We only care about the pin preferences here, the manage FDN contacts
60    // Preference is handled solely in xml.
61    private static final String BUTTON_FDN_ENABLE_KEY = "button_fdn_enable_key";
62    private static final String BUTTON_CHANGE_PIN2_KEY = "button_change_pin2_key";
63
64    private EditPinPreference mButtonEnableFDN;
65    private EditPinPreference mButtonChangePin2;
66
67    // State variables
68    private String mOldPin;
69    private String mNewPin;
70    private String mPuk2;
71    private static final int PIN_CHANGE_OLD = 0;
72    private static final int PIN_CHANGE_NEW = 1;
73    private static final int PIN_CHANGE_REENTER = 2;
74    private static final int PIN_CHANGE_PUK = 3;
75    private static final int PIN_CHANGE_NEW_PIN_FOR_PUK = 4;
76    private static final int PIN_CHANGE_REENTER_PIN_FOR_PUK = 5;
77    private int mPinChangeState;
78    private boolean mIsPuk2Locked;    // Indicates we know that we are PUK2 blocked.
79
80    private static final String SKIP_OLD_PIN_KEY = "skip_old_pin_key";
81    private static final String PIN_CHANGE_STATE_KEY = "pin_change_state_key";
82    private static final String OLD_PIN_KEY = "old_pin_key";
83    private static final String NEW_PIN_KEY = "new_pin_key";
84    private static final String DIALOG_MESSAGE_KEY = "dialog_message_key";
85    private static final String DIALOG_PIN_ENTRY_KEY = "dialog_pin_entry_key";
86
87    // size limits for the pin.
88    private static final int MIN_PIN_LENGTH = 4;
89    private static final int MAX_PIN_LENGTH = 8;
90
91    /**
92     * Delegate to the respective handlers.
93     */
94    @Override
95    public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
96        if (preference == mButtonEnableFDN) {
97            toggleFDNEnable(positiveResult);
98        } else if (preference == mButtonChangePin2){
99            updatePINChangeState(positiveResult);
100        }
101    }
102
103    /**
104     * Attempt to toggle FDN activation.
105     */
106    private void toggleFDNEnable(boolean positiveResult) {
107        if (!positiveResult) {
108            return;
109        }
110
111        // validate the pin first, before submitting it to the RIL for FDN enable.
112        String password = mButtonEnableFDN.getText();
113        if (validatePin (password, false)) {
114            // get the relevant data for the icc call
115            boolean isEnabled = mPhone.getIccCard().getIccFdnEnabled();
116            Message onComplete = mFDNHandler.obtainMessage(EVENT_PIN2_ENTRY_COMPLETE);
117
118            // make fdn request
119            mPhone.getIccCard().setIccFdnEnabled(!isEnabled, password, onComplete);
120        } else {
121            // throw up error if the pin is invalid.
122            displayMessage(R.string.invalidPin2);
123        }
124
125        mButtonEnableFDN.setText("");
126    }
127
128    /**
129     * Attempt to change the pin.
130     */
131    private void updatePINChangeState(boolean positiveResult) {
132        if (DBG) log("updatePINChangeState positive=" + positiveResult
133                + " mPinChangeState=" + mPinChangeState
134                + " mSkipOldPin=" + mIsPuk2Locked);
135
136        if (!positiveResult) {
137            // reset the state on cancel, either to expect PUK2 or PIN2
138            if (!mIsPuk2Locked) {
139                resetPinChangeState();
140            } else {
141                resetPinChangeStateForPUK2();
142            }
143            return;
144        }
145
146        // Progress through the dialog states, generally in this order:
147        //   1. Enter old pin
148        //   2. Enter new pin
149        //   3. Re-Enter new pin
150        // While handling any error conditions that may show up in between.
151        // Also handle the PUK2 entry, if it is requested.
152        //
153        // In general, if any invalid entries are made, the dialog re-
154        // appears with text to indicate what the issue is.
155        switch (mPinChangeState) {
156            case PIN_CHANGE_OLD:
157                mOldPin = mButtonChangePin2.getText();
158                mButtonChangePin2.setText("");
159                // if the pin is not valid, display a message and reset the state.
160                if (validatePin (mOldPin, false)) {
161                    mPinChangeState = PIN_CHANGE_NEW;
162                    displayPinChangeDialog();
163                } else {
164                    displayPinChangeDialog(R.string.invalidPin2, true);
165                }
166                break;
167            case PIN_CHANGE_NEW:
168                mNewPin = mButtonChangePin2.getText();
169                mButtonChangePin2.setText("");
170                // if the new pin is not valid, display a message and reset the state.
171                if (validatePin (mNewPin, false)) {
172                    mPinChangeState = PIN_CHANGE_REENTER;
173                    displayPinChangeDialog();
174                } else {
175                    displayPinChangeDialog(R.string.invalidPin2, true);
176                }
177                break;
178            case PIN_CHANGE_REENTER:
179                // if the re-entered pin is not valid, display a message and reset the state.
180                if (!mNewPin.equals(mButtonChangePin2.getText())) {
181                    mPinChangeState = PIN_CHANGE_NEW;
182                    mButtonChangePin2.setText("");
183                    displayPinChangeDialog(R.string.mismatchPin2, true);
184                } else {
185                    // If the PIN is valid, then we submit the change PIN request.
186                    mButtonChangePin2.setText("");
187                    Message onComplete = mFDNHandler.obtainMessage(
188                            EVENT_PIN2_CHANGE_COMPLETE);
189                    mPhone.getIccCard().changeIccFdnPassword(
190                            mOldPin, mNewPin, onComplete);
191                }
192                break;
193            case PIN_CHANGE_PUK: {
194                    // Doh! too many incorrect requests, PUK requested.
195                    mPuk2 = mButtonChangePin2.getText();
196                    mButtonChangePin2.setText("");
197                    // if the puk is not valid, display
198                    // a message and reset the state.
199                    if (validatePin (mPuk2, true)) {
200                        mPinChangeState = PIN_CHANGE_NEW_PIN_FOR_PUK;
201                        displayPinChangeDialog();
202                    } else {
203                        displayPinChangeDialog(R.string.invalidPuk2, true);
204                    }
205                }
206                break;
207            case PIN_CHANGE_NEW_PIN_FOR_PUK:
208                mNewPin = mButtonChangePin2.getText();
209                mButtonChangePin2.setText("");
210                // if the new pin is not valid, display
211                // a message and reset the state.
212                if (validatePin (mNewPin, false)) {
213                    mPinChangeState = PIN_CHANGE_REENTER_PIN_FOR_PUK;
214                    displayPinChangeDialog();
215                } else {
216                    displayPinChangeDialog(R.string.invalidPin2, true);
217                }
218                break;
219            case PIN_CHANGE_REENTER_PIN_FOR_PUK:
220                // if the re-entered pin is not valid, display
221                // a message and reset the state.
222                if (!mNewPin.equals(mButtonChangePin2.getText())) {
223                    mPinChangeState = PIN_CHANGE_NEW_PIN_FOR_PUK;
224                    mButtonChangePin2.setText("");
225                    displayPinChangeDialog(R.string.mismatchPin2, true);
226                } else {
227                    // Both puk2 and new pin2 are ready to submit
228                    mButtonChangePin2.setText("");
229                    Message onComplete = mFDNHandler.obtainMessage(
230                            EVENT_PIN2_CHANGE_COMPLETE);
231                    mPhone.getIccCard().supplyPuk2(mPuk2, mNewPin, onComplete);
232                }
233                break;
234        }
235    }
236
237    /**
238     * Handler for asynchronous replies from the sim.
239     */
240    private final Handler mFDNHandler = new Handler() {
241        @Override
242        public void handleMessage(Message msg) {
243            switch (msg.what) {
244
245                // when we are enabling FDN, either we are unsuccessful and display
246                // a toast, or just update the UI.
247                case EVENT_PIN2_ENTRY_COMPLETE: {
248                        AsyncResult ar = (AsyncResult) msg.obj;
249                        if (ar.exception != null && ar.exception instanceof CommandException) {
250                            int attemptsRemaining = msg.arg1;
251                            // see if PUK2 is requested and alert the user accordingly.
252                            CommandException.Error e =
253                                    ((CommandException) ar.exception).getCommandError();
254                            switch (e) {
255                                case SIM_PUK2:
256                                    // make sure we set the PUK2 state so that we can skip
257                                    // some redundant behaviour.
258                                    displayMessage(R.string.fdn_enable_puk2_requested,
259                                            attemptsRemaining);
260                                    resetPinChangeStateForPUK2();
261                                    break;
262                                case PASSWORD_INCORRECT:
263                                    displayMessage(R.string.pin2_invalid, attemptsRemaining);
264                                    break;
265                                default:
266                                    displayMessage(R.string.fdn_failed, attemptsRemaining);
267                                    break;
268                            }
269                        }
270                        updateEnableFDN();
271                    }
272                    break;
273
274                // when changing the pin we need to pay attention to whether or not
275                // the error requests a PUK (usually after too many incorrect tries)
276                // Set the state accordingly.
277                case EVENT_PIN2_CHANGE_COMPLETE: {
278                        if (DBG)
279                            log("Handle EVENT_PIN2_CHANGE_COMPLETE");
280                        AsyncResult ar = (AsyncResult) msg.obj;
281                        if (ar.exception != null) {
282                            int attemptsRemaining = msg.arg1;
283                            log("Handle EVENT_PIN2_CHANGE_COMPLETE attemptsRemaining="
284                                    + attemptsRemaining);
285                            CommandException ce = (CommandException) ar.exception;
286                            if (ce.getCommandError() == CommandException.Error.SIM_PUK2) {
287                                // throw an alert dialog on the screen, displaying the
288                                // request for a PUK2.  set the cancel listener to
289                                // FdnSetting.onCancel().
290                                AlertDialog a = new AlertDialog.Builder(FdnSetting.this)
291                                    .setMessage(R.string.puk2_requested)
292                                    .setCancelable(true)
293                                    .setOnCancelListener(FdnSetting.this)
294                                    .create();
295                                a.getWindow().addFlags(
296                                        WindowManager.LayoutParams.FLAG_DIM_BEHIND);
297                                a.show();
298                            } else {
299                                // set the correct error message depending upon the state.
300                                // Reset the state depending upon or knowledge of the PUK state.
301                                if (!mIsPuk2Locked) {
302                                    displayMessage(R.string.badPin2, attemptsRemaining);
303                                    resetPinChangeState();
304                                } else {
305                                    displayMessage(R.string.badPuk2, attemptsRemaining);
306                                    resetPinChangeStateForPUK2();
307                                }
308                            }
309                        } else {
310                            if (mPinChangeState == PIN_CHANGE_PUK) {
311                                displayMessage(R.string.pin2_unblocked);
312                            } else {
313                                displayMessage(R.string.pin2_changed);
314                            }
315
316                            // reset to normal behaviour on successful change.
317                            resetPinChangeState();
318                        }
319                    }
320                    break;
321            }
322        }
323    };
324
325    /**
326     * Cancel listener for the PUK2 request alert dialog.
327     */
328    @Override
329    public void onCancel(DialogInterface dialog) {
330        // set the state of the preference and then display the dialog.
331        resetPinChangeStateForPUK2();
332        displayPinChangeDialog(0, true);
333    }
334
335    /**
336     * Display a toast for message, like the rest of the settings.
337     */
338    private final void displayMessage(int strId, int attemptsRemaining) {
339        String s = getString(strId);
340        if ((strId == R.string.badPin2) || (strId == R.string.badPuk2) ||
341                (strId == R.string.pin2_invalid)) {
342            if (attemptsRemaining >= 0) {
343                s = getString(strId) + getString(R.string.pin2_attempts, attemptsRemaining);
344            } else {
345                s = getString(strId);
346            }
347        }
348        log("displayMessage: attemptsRemaining=" + attemptsRemaining + " s=" + s);
349        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
350    }
351
352    private final void displayMessage(int strId) {
353        displayMessage(strId, -1);
354    }
355
356    /**
357     * The next two functions are for updating the message field on the dialog.
358     */
359    private final void displayPinChangeDialog() {
360        displayPinChangeDialog(0, true);
361    }
362
363    private final void displayPinChangeDialog(int strId, boolean shouldDisplay) {
364        int msgId;
365        switch (mPinChangeState) {
366            case PIN_CHANGE_OLD:
367                msgId = R.string.oldPin2Label;
368                break;
369            case PIN_CHANGE_NEW:
370            case PIN_CHANGE_NEW_PIN_FOR_PUK:
371                msgId = R.string.newPin2Label;
372                break;
373            case PIN_CHANGE_REENTER:
374            case PIN_CHANGE_REENTER_PIN_FOR_PUK:
375                msgId = R.string.confirmPin2Label;
376                break;
377            case PIN_CHANGE_PUK:
378            default:
379                msgId = R.string.label_puk2_code;
380                break;
381        }
382
383        // append the note / additional message, if needed.
384        if (strId != 0) {
385            mButtonChangePin2.setDialogMessage(getText(msgId) + "\n" + getText(strId));
386        } else {
387            mButtonChangePin2.setDialogMessage(msgId);
388        }
389
390        // only display if requested.
391        if (shouldDisplay) {
392            mButtonChangePin2.showPinDialog();
393        }
394    }
395
396    /**
397     * Reset the state of the pin change dialog.
398     */
399    private final void resetPinChangeState() {
400        if (DBG) log("resetPinChangeState");
401        mPinChangeState = PIN_CHANGE_OLD;
402        displayPinChangeDialog(0, false);
403        mOldPin = mNewPin = "";
404        mIsPuk2Locked = false;
405    }
406
407    /**
408     * Reset the state of the pin change dialog solely for PUK2 use.
409     */
410    private final void resetPinChangeStateForPUK2() {
411        if (DBG) log("resetPinChangeStateForPUK2");
412        mPinChangeState = PIN_CHANGE_PUK;
413        displayPinChangeDialog(0, false);
414        mOldPin = mNewPin = mPuk2 = "";
415        mIsPuk2Locked = true;
416    }
417
418    /**
419     * Validate the pin entry.
420     *
421     * @param pin This is the pin to validate
422     * @param isPuk Boolean indicating whether we are to treat
423     * the pin input as a puk.
424     */
425    private boolean validatePin(String pin, boolean isPuk) {
426
427        // for pin, we have 4-8 numbers, or puk, we use only 8.
428        int pinMinimum = isPuk ? MAX_PIN_LENGTH : MIN_PIN_LENGTH;
429
430        // check validity
431        if (pin == null || pin.length() < pinMinimum || pin.length() > MAX_PIN_LENGTH) {
432            return false;
433        } else {
434            return true;
435        }
436    }
437
438    /**
439     * Reflect the updated FDN state in the UI.
440     */
441    private void updateEnableFDN() {
442        if (mPhone.getIccCard().getIccFdnEnabled()) {
443            mButtonEnableFDN.setTitle(R.string.enable_fdn_ok);
444            mButtonEnableFDN.setSummary(R.string.fdn_enabled);
445            mButtonEnableFDN.setDialogTitle(R.string.disable_fdn);
446        } else {
447            mButtonEnableFDN.setTitle(R.string.disable_fdn_ok);
448            mButtonEnableFDN.setSummary(R.string.fdn_disabled);
449            mButtonEnableFDN.setDialogTitle(R.string.enable_fdn);
450        }
451    }
452
453    @Override
454    protected void onCreate(Bundle icicle) {
455        super.onCreate(icicle);
456
457        addPreferencesFromResource(R.xml.fdn_setting);
458
459        mPhone = PhoneGlobals.getPhone();
460
461        //get UI object references
462        PreferenceScreen prefSet = getPreferenceScreen();
463        mButtonEnableFDN = (EditPinPreference) prefSet.findPreference(BUTTON_FDN_ENABLE_KEY);
464        mButtonChangePin2 = (EditPinPreference) prefSet.findPreference(BUTTON_CHANGE_PIN2_KEY);
465
466        //assign click listener and update state
467        mButtonEnableFDN.setOnPinEnteredListener(this);
468        updateEnableFDN();
469
470        mButtonChangePin2.setOnPinEnteredListener(this);
471
472        // Only reset the pin change dialog if we're not in the middle of changing it.
473        if (icicle == null) {
474            resetPinChangeState();
475        } else {
476            mIsPuk2Locked = icicle.getBoolean(SKIP_OLD_PIN_KEY);
477            mPinChangeState = icicle.getInt(PIN_CHANGE_STATE_KEY);
478            mOldPin = icicle.getString(OLD_PIN_KEY);
479            mNewPin = icicle.getString(NEW_PIN_KEY);
480            mButtonChangePin2.setDialogMessage(icicle.getString(DIALOG_MESSAGE_KEY));
481            mButtonChangePin2.setText(icicle.getString(DIALOG_PIN_ENTRY_KEY));
482        }
483
484        ActionBar actionBar = getActionBar();
485        if (actionBar != null) {
486            // android.R.id.home will be triggered in onOptionsItemSelected()
487            actionBar.setDisplayHomeAsUpEnabled(true);
488        }
489    }
490
491    @Override
492    protected void onResume() {
493        super.onResume();
494        mPhone = PhoneGlobals.getPhone();
495        updateEnableFDN();
496    }
497
498    /**
499     * Save the state of the pin change.
500     */
501    @Override
502    protected void onSaveInstanceState(Bundle out) {
503        super.onSaveInstanceState(out);
504        out.putBoolean(SKIP_OLD_PIN_KEY, mIsPuk2Locked);
505        out.putInt(PIN_CHANGE_STATE_KEY, mPinChangeState);
506        out.putString(OLD_PIN_KEY, mOldPin);
507        out.putString(NEW_PIN_KEY, mNewPin);
508        out.putString(DIALOG_MESSAGE_KEY, mButtonChangePin2.getDialogMessage().toString());
509        out.putString(DIALOG_PIN_ENTRY_KEY, mButtonChangePin2.getText());
510    }
511
512    @Override
513    public boolean onOptionsItemSelected(MenuItem item) {
514        final int itemId = item.getItemId();
515        if (itemId == android.R.id.home) {  // See ActionBar#setDisplayHomeAsUpEnabled()
516            CallFeaturesSetting.goUpToTopLevelSetting(this);
517            return true;
518        }
519        return super.onOptionsItemSelected(item);
520    }
521
522    private void log(String msg) {
523        Log.d(LOG_TAG, "FdnSetting: " + msg);
524    }
525}
526
527