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 
17 package com.android.phone.settings.fdn;
18 
19 import android.app.ActionBar;
20 import android.app.AlertDialog;
21 import android.content.DialogInterface;
22 import android.os.AsyncResult;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.preference.PreferenceActivity;
27 import android.preference.PreferenceScreen;
28 import android.util.Log;
29 import android.view.MenuItem;
30 import android.view.WindowManager;
31 import android.widget.Toast;
32 
33 import com.android.internal.telephony.CommandException;
34 import com.android.internal.telephony.Phone;
35 import com.android.phone.CallFeaturesSetting;
36 import com.android.phone.PhoneGlobals;
37 import com.android.phone.R;
38 import 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  */
44 public 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
onPinEntered(EditPinPreference preference, boolean positiveResult)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      */
toggleFDNEnable(boolean positiveResult)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      */
updatePINChangeState(boolean positiveResult)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
onCancel(DialogInterface dialog)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      */
displayMessage(int strId, int attemptsRemaining)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 
displayMessage(int strId)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      */
displayPinChangeDialog()377     private final void displayPinChangeDialog() {
378         displayPinChangeDialog(0, true);
379     }
380 
displayPinChangeDialog(int strId, boolean shouldDisplay)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      */
resetPinChangeState()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      */
resetPinChangeStateForPUK2()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      */
validatePin(String pin, boolean isPuk)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      */
updateEnableFDN()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     */
updateChangePIN2()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
onCreate(Bundle icicle)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
onResume()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
onSaveInstanceState(Bundle out)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
onOptionsItemSelected(MenuItem item)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 
log(String msg)562     private void log(String msg) {
563         Log.d(LOG_TAG, "FdnSetting: " + msg);
564     }
565 }
566 
567