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