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