1 /*
2  * Copyright (C) 2016 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.voicemail.impl.settings;
18 
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.ProgressDialog;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.DialogInterface.OnDismissListener;
26 import android.net.Network;
27 import android.os.Build.VERSION_CODES;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.support.annotation.Nullable;
32 import android.telecom.PhoneAccountHandle;
33 import android.text.Editable;
34 import android.text.InputFilter;
35 import android.text.InputFilter.LengthFilter;
36 import android.text.TextWatcher;
37 import android.view.KeyEvent;
38 import android.view.MenuItem;
39 import android.view.View;
40 import android.view.View.OnClickListener;
41 import android.view.WindowManager;
42 import android.view.inputmethod.EditorInfo;
43 import android.widget.Button;
44 import android.widget.EditText;
45 import android.widget.TextView;
46 import android.widget.TextView.OnEditorActionListener;
47 import android.widget.Toast;
48 import com.android.dialer.logging.DialerImpression;
49 import com.android.voicemail.impl.OmtpConstants;
50 import com.android.voicemail.impl.OmtpConstants.ChangePinResult;
51 import com.android.voicemail.impl.OmtpEvents;
52 import com.android.voicemail.impl.OmtpVvmCarrierConfigHelper;
53 import com.android.voicemail.impl.R;
54 import com.android.voicemail.impl.VisualVoicemailPreferences;
55 import com.android.voicemail.impl.VoicemailStatus;
56 import com.android.voicemail.impl.VvmLog;
57 import com.android.voicemail.impl.imap.ImapHelper;
58 import com.android.voicemail.impl.imap.ImapHelper.InitializingException;
59 import com.android.voicemail.impl.mail.MessagingException;
60 import com.android.voicemail.impl.sync.VvmNetworkRequestCallback;
61 import com.android.voicemail.impl.utils.LoggerUtils;
62 
63 /**
64  * Dialog to change the voicemail PIN. The TUI (Telephony User Interface) PIN is used when accessing
65  * traditional voicemail through phone call. The intent to launch this activity must contain {@link
66  * #EXTRA_PHONE_ACCOUNT_HANDLE}
67  */
68 @TargetApi(VERSION_CODES.O)
69 public class VoicemailChangePinActivity extends Activity
70     implements OnClickListener, OnEditorActionListener, TextWatcher {
71 
72   private static final String TAG = "VmChangePinActivity";
73 
74   public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "extra_phone_account_handle";
75 
76   private static final String KEY_DEFAULT_OLD_PIN = "default_old_pin";
77 
78   private static final int MESSAGE_HANDLE_RESULT = 1;
79 
80   private PhoneAccountHandle mPhoneAccountHandle;
81   private OmtpVvmCarrierConfigHelper mConfig;
82 
83   private int mPinMinLength;
84   private int mPinMaxLength;
85 
86   private State mUiState = State.Initial;
87   private String mOldPin;
88   private String mFirstPin;
89 
90   private ProgressDialog mProgressDialog;
91 
92   private TextView mHeaderText;
93   private TextView mHintText;
94   private TextView mErrorText;
95   private EditText mPinEntry;
96   private Button mCancelButton;
97   private Button mNextButton;
98 
99   private Handler mHandler =
100       new Handler() {
101         @Override
102         public void handleMessage(Message message) {
103           if (message.what == MESSAGE_HANDLE_RESULT) {
104             mUiState.handleResult(VoicemailChangePinActivity.this, message.arg1);
105           }
106         }
107       };
108 
109   private enum State {
110     /**
111      * Empty state to handle initial state transition. Will immediately switch into {@link
112      * #VerifyOldPin} if a default PIN has been set by the OMTP client, or {@link #EnterOldPin} if
113      * not.
114      */
115     Initial,
116     /**
117      * Prompt the user to enter old PIN. The PIN will be verified with the server before proceeding
118      * to {@link #EnterNewPin}.
119      */
120     EnterOldPin {
121       @Override
onEnter(VoicemailChangePinActivity activity)122       public void onEnter(VoicemailChangePinActivity activity) {
123         activity.setHeader(R.string.change_pin_enter_old_pin_header);
124         activity.mHintText.setText(R.string.change_pin_enter_old_pin_hint);
125         activity.mNextButton.setText(R.string.change_pin_continue_label);
126         activity.mErrorText.setText(null);
127       }
128 
129       @Override
onInputChanged(VoicemailChangePinActivity activity)130       public void onInputChanged(VoicemailChangePinActivity activity) {
131         activity.setNextEnabled(activity.getCurrentPasswordInput().length() > 0);
132       }
133 
134       @Override
handleNext(VoicemailChangePinActivity activity)135       public void handleNext(VoicemailChangePinActivity activity) {
136         activity.mOldPin = activity.getCurrentPasswordInput();
137         activity.verifyOldPin();
138       }
139 
140       @Override
handleResult(VoicemailChangePinActivity activity, @ChangePinResult int result)141       public void handleResult(VoicemailChangePinActivity activity, @ChangePinResult int result) {
142         if (result == OmtpConstants.CHANGE_PIN_SUCCESS) {
143           activity.updateState(State.EnterNewPin);
144         } else {
145           CharSequence message = activity.getChangePinResultMessage(result);
146           activity.showError(message);
147           activity.mPinEntry.setText("");
148         }
149       }
150     },
151     /**
152      * The default old PIN is found. Show a blank screen while verifying with the server to make
153      * sure the PIN is still valid. If the PIN is still valid, proceed to {@link #EnterNewPin}. If
154      * not, the user probably changed the PIN through other means, proceed to {@link #EnterOldPin}.
155      * If any other issue caused the verifying to fail, show an error and exit.
156      */
157     VerifyOldPin {
158       @Override
onEnter(VoicemailChangePinActivity activity)159       public void onEnter(VoicemailChangePinActivity activity) {
160         activity.findViewById(android.R.id.content).setVisibility(View.INVISIBLE);
161         activity.verifyOldPin();
162       }
163 
164       @Override
handleResult( final VoicemailChangePinActivity activity, @ChangePinResult int result)165       public void handleResult(
166           final VoicemailChangePinActivity activity, @ChangePinResult int result) {
167         if (result == OmtpConstants.CHANGE_PIN_SUCCESS) {
168           activity.updateState(State.EnterNewPin);
169         } else if (result == OmtpConstants.CHANGE_PIN_SYSTEM_ERROR) {
170           activity
171               .getWindow()
172               .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
173           activity.showError(
174               activity.getString(R.string.change_pin_system_error),
175               new OnDismissListener() {
176                 @Override
177                 public void onDismiss(DialogInterface dialog) {
178                   activity.finish();
179                 }
180               });
181         } else {
182           VvmLog.e(TAG, "invalid default old PIN: " + activity.getChangePinResultMessage(result));
183           // If the default old PIN is rejected by the server, the PIN is probably changed
184           // through other means, or the generated pin is invalid
185           // Wipe the default old PIN so the old PIN input box will be shown to the user
186           // on the next time.
187           setDefaultOldPIN(activity, activity.mPhoneAccountHandle, null);
188           activity.handleOmtpEvent(OmtpEvents.CONFIG_PIN_SET);
189           activity.updateState(State.EnterOldPin);
190         }
191       }
192 
193       @Override
onLeave(VoicemailChangePinActivity activity)194       public void onLeave(VoicemailChangePinActivity activity) {
195         activity.findViewById(android.R.id.content).setVisibility(View.VISIBLE);
196       }
197     },
198     /**
199      * Let the user enter the new PIN and validate the format. Only length is enforced, PIN strength
200      * check relies on the server. After a valid PIN is entered, proceed to {@link #ConfirmNewPin}
201      */
202     EnterNewPin {
203       @Override
onEnter(VoicemailChangePinActivity activity)204       public void onEnter(VoicemailChangePinActivity activity) {
205         activity.mHeaderText.setText(R.string.change_pin_enter_new_pin_header);
206         activity.mNextButton.setText(R.string.change_pin_continue_label);
207         activity.mHintText.setText(
208             activity.getString(
209                 R.string.change_pin_enter_new_pin_hint,
210                 activity.mPinMinLength,
211                 activity.mPinMaxLength));
212       }
213 
214       @Override
onInputChanged(VoicemailChangePinActivity activity)215       public void onInputChanged(VoicemailChangePinActivity activity) {
216         String password = activity.getCurrentPasswordInput();
217         if (password.length() == 0) {
218           activity.setNextEnabled(false);
219           return;
220         }
221         CharSequence error = activity.validatePassword(password);
222         if (error != null) {
223           activity.mErrorText.setText(error);
224           activity.setNextEnabled(false);
225         } else {
226           activity.mErrorText.setText(null);
227           activity.setNextEnabled(true);
228         }
229       }
230 
231       @Override
handleNext(VoicemailChangePinActivity activity)232       public void handleNext(VoicemailChangePinActivity activity) {
233         CharSequence errorMsg;
234         errorMsg = activity.validatePassword(activity.getCurrentPasswordInput());
235         if (errorMsg != null) {
236           activity.showError(errorMsg);
237           return;
238         }
239         activity.mFirstPin = activity.getCurrentPasswordInput();
240         activity.updateState(State.ConfirmNewPin);
241       }
242     },
243     /**
244      * Let the user type in the same PIN again to avoid typos. If the PIN matches then perform a PIN
245      * change to the server. Finish the activity if succeeded. Return to {@link #EnterOldPin} if the
246      * old PIN is rejected, {@link #EnterNewPin} for other failure.
247      */
248     ConfirmNewPin {
249       @Override
onEnter(VoicemailChangePinActivity activity)250       public void onEnter(VoicemailChangePinActivity activity) {
251         activity.mHeaderText.setText(R.string.change_pin_confirm_pin_header);
252         activity.mHintText.setText(null);
253         activity.mNextButton.setText(R.string.change_pin_ok_label);
254       }
255 
256       @Override
onInputChanged(VoicemailChangePinActivity activity)257       public void onInputChanged(VoicemailChangePinActivity activity) {
258         if (activity.getCurrentPasswordInput().length() == 0) {
259           activity.setNextEnabled(false);
260           return;
261         }
262         if (activity.getCurrentPasswordInput().equals(activity.mFirstPin)) {
263           activity.setNextEnabled(true);
264           activity.mErrorText.setText(null);
265         } else {
266           activity.setNextEnabled(false);
267           activity.mErrorText.setText(R.string.change_pin_confirm_pins_dont_match);
268         }
269       }
270 
271       @Override
handleResult(VoicemailChangePinActivity activity, @ChangePinResult int result)272       public void handleResult(VoicemailChangePinActivity activity, @ChangePinResult int result) {
273         if (result == OmtpConstants.CHANGE_PIN_SUCCESS) {
274           // If the PIN change succeeded we no longer know what the old (current) PIN is.
275           // Wipe the default old PIN so the old PIN input box will be shown to the user
276           // on the next time.
277           setDefaultOldPIN(activity, activity.mPhoneAccountHandle, null);
278           activity.handleOmtpEvent(OmtpEvents.CONFIG_PIN_SET);
279 
280           activity.finish();
281           LoggerUtils.logImpressionOnMainThread(
282               activity, DialerImpression.Type.VVM_CHANGE_PIN_COMPLETED);
283           Toast.makeText(
284                   activity, activity.getString(R.string.change_pin_succeeded), Toast.LENGTH_SHORT)
285               .show();
286         } else {
287           CharSequence message = activity.getChangePinResultMessage(result);
288           VvmLog.i(TAG, "Change PIN failed: " + message);
289           activity.showError(message);
290           if (result == OmtpConstants.CHANGE_PIN_MISMATCH) {
291             // Somehow the PIN has changed, prompt to enter the old PIN again.
292             activity.updateState(State.EnterOldPin);
293           } else {
294             // The new PIN failed to fulfil other restrictions imposed by the server.
295             activity.updateState(State.EnterNewPin);
296           }
297         }
298       }
299 
300       @Override
handleNext(VoicemailChangePinActivity activity)301       public void handleNext(VoicemailChangePinActivity activity) {
302         activity.processPinChange(activity.mOldPin, activity.mFirstPin);
303       }
304     };
305 
306     /** The activity has switched from another state to this one. */
onEnter(VoicemailChangePinActivity activity)307     public void onEnter(VoicemailChangePinActivity activity) {
308       // Do nothing
309     }
310 
311     /**
312      * The user has typed something into the PIN input field. Also called after {@link
313      * #onEnter(VoicemailChangePinActivity)}
314      */
onInputChanged(VoicemailChangePinActivity activity)315     public void onInputChanged(VoicemailChangePinActivity activity) {
316       // Do nothing
317     }
318 
319     /** The asynchronous call to change the PIN on the server has returned. */
handleResult(VoicemailChangePinActivity activity, @ChangePinResult int result)320     public void handleResult(VoicemailChangePinActivity activity, @ChangePinResult int result) {
321       // Do nothing
322     }
323 
324     /** The user has pressed the "next" button. */
handleNext(VoicemailChangePinActivity activity)325     public void handleNext(VoicemailChangePinActivity activity) {
326       // Do nothing
327     }
328 
329     /** The activity has switched from this state to another one. */
onLeave(VoicemailChangePinActivity activity)330     public void onLeave(VoicemailChangePinActivity activity) {
331       // Do nothing
332     }
333   }
334 
335   @Override
onCreate(Bundle savedInstanceState)336   public void onCreate(Bundle savedInstanceState) {
337     super.onCreate(savedInstanceState);
338 
339     mPhoneAccountHandle = getIntent().getParcelableExtra(EXTRA_PHONE_ACCOUNT_HANDLE);
340     mConfig = new OmtpVvmCarrierConfigHelper(this, mPhoneAccountHandle);
341     setContentView(R.layout.voicemail_change_pin);
342     setTitle(R.string.change_pin_title);
343 
344     readPinLength();
345 
346     View view = findViewById(android.R.id.content);
347 
348     mCancelButton = (Button) view.findViewById(R.id.cancel_button);
349     mCancelButton.setOnClickListener(this);
350     mNextButton = (Button) view.findViewById(R.id.next_button);
351     mNextButton.setOnClickListener(this);
352 
353     mPinEntry = (EditText) view.findViewById(R.id.pin_entry);
354     mPinEntry.setOnEditorActionListener(this);
355     mPinEntry.addTextChangedListener(this);
356     if (mPinMaxLength != 0) {
357       mPinEntry.setFilters(new InputFilter[] {new LengthFilter(mPinMaxLength)});
358     }
359 
360     mHeaderText = (TextView) view.findViewById(R.id.headerText);
361     mHintText = (TextView) view.findViewById(R.id.hintText);
362     mErrorText = (TextView) view.findViewById(R.id.errorText);
363 
364     if (isDefaultOldPinSet(this, mPhoneAccountHandle)) {
365       mOldPin = getDefaultOldPin(this, mPhoneAccountHandle);
366       updateState(State.VerifyOldPin);
367     } else {
368       updateState(State.EnterOldPin);
369     }
370   }
371 
handleOmtpEvent(OmtpEvents event)372   private void handleOmtpEvent(OmtpEvents event) {
373     mConfig.handleEvent(getVoicemailStatusEditor(), event);
374   }
375 
getVoicemailStatusEditor()376   private VoicemailStatus.Editor getVoicemailStatusEditor() {
377     // This activity does not have any automatic retry mechanism, errors should be written right
378     // away.
379     return VoicemailStatus.edit(this, mPhoneAccountHandle);
380   }
381 
382   /** Extracts the pin length requirement sent by the server with a STATUS SMS. */
readPinLength()383   private void readPinLength() {
384     VisualVoicemailPreferences preferences =
385         new VisualVoicemailPreferences(this, mPhoneAccountHandle);
386     // The OMTP pin length format is {min}-{max}
387     String[] lengths = preferences.getString(OmtpConstants.TUI_PASSWORD_LENGTH, "").split("-");
388     if (lengths.length == 2) {
389       try {
390         mPinMinLength = Integer.parseInt(lengths[0]);
391         mPinMaxLength = Integer.parseInt(lengths[1]);
392       } catch (NumberFormatException e) {
393         mPinMinLength = 0;
394         mPinMaxLength = 0;
395       }
396     } else {
397       mPinMinLength = 0;
398       mPinMaxLength = 0;
399     }
400   }
401 
402   @Override
onResume()403   public void onResume() {
404     super.onResume();
405     updateState(mUiState);
406   }
407 
handleNext()408   public void handleNext() {
409     if (mPinEntry.length() == 0) {
410       return;
411     }
412     mUiState.handleNext(this);
413   }
414 
415   @Override
onClick(View v)416   public void onClick(View v) {
417     if (v.getId() == R.id.next_button) {
418       handleNext();
419     } else if (v.getId() == R.id.cancel_button) {
420       finish();
421     }
422   }
423 
424   @Override
onOptionsItemSelected(MenuItem item)425   public boolean onOptionsItemSelected(MenuItem item) {
426     if (item.getItemId() == android.R.id.home) {
427       onBackPressed();
428       return true;
429     }
430     return super.onOptionsItemSelected(item);
431   }
432 
433   @Override
onEditorAction(TextView v, int actionId, KeyEvent event)434   public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
435     if (!mNextButton.isEnabled()) {
436       return true;
437     }
438     // Check if this was the result of hitting the enter or "done" key
439     if (actionId == EditorInfo.IME_NULL
440         || actionId == EditorInfo.IME_ACTION_DONE
441         || actionId == EditorInfo.IME_ACTION_NEXT) {
442       handleNext();
443       return true;
444     }
445     return false;
446   }
447 
448   @Override
afterTextChanged(Editable s)449   public void afterTextChanged(Editable s) {
450     mUiState.onInputChanged(this);
451   }
452 
453   @Override
beforeTextChanged(CharSequence s, int start, int count, int after)454   public void beforeTextChanged(CharSequence s, int start, int count, int after) {
455     // Do nothing
456   }
457 
458   @Override
onTextChanged(CharSequence s, int start, int before, int count)459   public void onTextChanged(CharSequence s, int start, int before, int count) {
460     // Do nothing
461   }
462 
463   /**
464    * After replacing the default PIN with a random PIN, call this to store the random PIN. The
465    * stored PIN will be automatically entered when the user attempts to change the PIN.
466    */
setDefaultOldPIN( Context context, PhoneAccountHandle phoneAccountHandle, String pin)467   public static void setDefaultOldPIN(
468       Context context, PhoneAccountHandle phoneAccountHandle, String pin) {
469     new VisualVoicemailPreferences(context, phoneAccountHandle)
470         .edit()
471         .putString(KEY_DEFAULT_OLD_PIN, pin)
472         .apply();
473   }
474 
isDefaultOldPinSet(Context context, PhoneAccountHandle phoneAccountHandle)475   public static boolean isDefaultOldPinSet(Context context, PhoneAccountHandle phoneAccountHandle) {
476     return getDefaultOldPin(context, phoneAccountHandle) != null;
477   }
478 
getDefaultOldPin(Context context, PhoneAccountHandle phoneAccountHandle)479   private static String getDefaultOldPin(Context context, PhoneAccountHandle phoneAccountHandle) {
480     return new VisualVoicemailPreferences(context, phoneAccountHandle)
481         .getString(KEY_DEFAULT_OLD_PIN);
482   }
483 
getCurrentPasswordInput()484   private String getCurrentPasswordInput() {
485     return mPinEntry.getText().toString();
486   }
487 
updateState(State state)488   private void updateState(State state) {
489     State previousState = mUiState;
490     mUiState = state;
491     if (previousState != state) {
492       previousState.onLeave(this);
493       mPinEntry.setText("");
494       mUiState.onEnter(this);
495     }
496     mUiState.onInputChanged(this);
497   }
498 
499   /**
500    * Validates PIN and returns a message to display if PIN fails test.
501    *
502    * @param password the raw password the user typed in
503    * @return error message to show to user or null if password is OK
504    */
validatePassword(String password)505   private CharSequence validatePassword(String password) {
506     if (mPinMinLength == 0 && mPinMaxLength == 0) {
507       // Invalid length requirement is sent by the server, just accept anything and let the
508       // server decide.
509       return null;
510     }
511 
512     if (password.length() < mPinMinLength) {
513       return getString(R.string.vm_change_pin_error_too_short);
514     }
515     return null;
516   }
517 
setHeader(int text)518   private void setHeader(int text) {
519     mHeaderText.setText(text);
520     mPinEntry.setContentDescription(mHeaderText.getText());
521   }
522 
523   /**
524    * Get the corresponding message for the {@link ChangePinResult}.<code>result</code> must not
525    * {@link OmtpConstants#CHANGE_PIN_SUCCESS}
526    */
getChangePinResultMessage(@hangePinResult int result)527   private CharSequence getChangePinResultMessage(@ChangePinResult int result) {
528     switch (result) {
529       case OmtpConstants.CHANGE_PIN_TOO_SHORT:
530         return getString(R.string.vm_change_pin_error_too_short);
531       case OmtpConstants.CHANGE_PIN_TOO_LONG:
532         return getString(R.string.vm_change_pin_error_too_long);
533       case OmtpConstants.CHANGE_PIN_TOO_WEAK:
534         return getString(R.string.vm_change_pin_error_too_weak);
535       case OmtpConstants.CHANGE_PIN_INVALID_CHARACTER:
536         return getString(R.string.vm_change_pin_error_invalid);
537       case OmtpConstants.CHANGE_PIN_MISMATCH:
538         return getString(R.string.vm_change_pin_error_mismatch);
539       case OmtpConstants.CHANGE_PIN_SYSTEM_ERROR:
540         return getString(R.string.vm_change_pin_error_system_error);
541       default:
542         VvmLog.wtf(TAG, "Unexpected ChangePinResult " + result);
543         return null;
544     }
545   }
546 
verifyOldPin()547   private void verifyOldPin() {
548     processPinChange(mOldPin, mOldPin);
549   }
550 
setNextEnabled(boolean enabled)551   private void setNextEnabled(boolean enabled) {
552     mNextButton.setEnabled(enabled);
553   }
554 
showError(CharSequence message)555   private void showError(CharSequence message) {
556     showError(message, null);
557   }
558 
showError(CharSequence message, @Nullable OnDismissListener callback)559   private void showError(CharSequence message, @Nullable OnDismissListener callback) {
560     new AlertDialog.Builder(this)
561         .setMessage(message)
562         .setPositiveButton(android.R.string.ok, null)
563         .setOnDismissListener(callback)
564         .show();
565   }
566 
567   /** Asynchronous call to change the PIN on the server. */
processPinChange(String oldPin, String newPin)568   private void processPinChange(String oldPin, String newPin) {
569     mProgressDialog = new ProgressDialog(this);
570     mProgressDialog.setCancelable(false);
571     mProgressDialog.setMessage(getString(R.string.vm_change_pin_progress_message));
572     mProgressDialog.show();
573 
574     ChangePinNetworkRequestCallback callback = new ChangePinNetworkRequestCallback(oldPin, newPin);
575     callback.requestNetwork();
576   }
577 
578   private class ChangePinNetworkRequestCallback extends VvmNetworkRequestCallback {
579 
580     private final String mOldPin;
581     private final String mNewPin;
582 
ChangePinNetworkRequestCallback(String oldPin, String newPin)583     public ChangePinNetworkRequestCallback(String oldPin, String newPin) {
584       super(
585           mConfig, mPhoneAccountHandle, VoicemailChangePinActivity.this.getVoicemailStatusEditor());
586       mOldPin = oldPin;
587       mNewPin = newPin;
588     }
589 
590     @Override
onAvailable(Network network)591     public void onAvailable(Network network) {
592       super.onAvailable(network);
593       try (ImapHelper helper =
594           new ImapHelper(
595               VoicemailChangePinActivity.this,
596               mPhoneAccountHandle,
597               network,
598               getVoicemailStatusEditor())) {
599 
600         @ChangePinResult int result = helper.changePin(mOldPin, mNewPin);
601         sendResult(result);
602       } catch (InitializingException | MessagingException e) {
603         VvmLog.e(TAG, "ChangePinNetworkRequestCallback: onAvailable: ", e);
604         sendResult(OmtpConstants.CHANGE_PIN_SYSTEM_ERROR);
605       }
606     }
607 
608     @Override
onFailed(String reason)609     public void onFailed(String reason) {
610       super.onFailed(reason);
611       sendResult(OmtpConstants.CHANGE_PIN_SYSTEM_ERROR);
612     }
613 
sendResult(@hangePinResult int result)614     private void sendResult(@ChangePinResult int result) {
615       VvmLog.i(TAG, "Change PIN result: " + result);
616       if (mProgressDialog.isShowing()
617           && !VoicemailChangePinActivity.this.isDestroyed()
618           && !VoicemailChangePinActivity.this.isFinishing()) {
619         mProgressDialog.dismiss();
620       } else {
621         VvmLog.i(TAG, "Dialog not visible, not dismissing");
622       }
623       mHandler.obtainMessage(MESSAGE_HANDLE_RESULT, result, 0).sendToTarget();
624       releaseNetwork();
625     }
626   }
627 }
628