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