• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.settings.password;
18 
19 import android.annotation.Nullable;
20 import android.app.admin.DevicePolicyManager;
21 import android.app.settings.SettingsEnums;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.graphics.Typeface;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.os.CountDownTimer;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.os.SystemClock;
31 import android.os.UserManager;
32 import android.os.storage.StorageManager;
33 import android.text.Editable;
34 import android.text.InputType;
35 import android.text.TextUtils;
36 import android.view.KeyEvent;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.view.View.OnClickListener;
40 import android.view.ViewGroup;
41 import android.view.animation.AnimationUtils;
42 import android.view.inputmethod.EditorInfo;
43 import android.view.inputmethod.InputMethodManager;
44 import android.widget.ImeAwareEditText;
45 import android.widget.TextView;
46 import android.widget.TextView.OnEditorActionListener;
47 
48 import androidx.fragment.app.Fragment;
49 
50 import com.android.internal.widget.LockPatternChecker;
51 import com.android.internal.widget.LockPatternUtils;
52 import com.android.internal.widget.LockscreenCredential;
53 import com.android.internal.widget.TextViewInputDisabler;
54 import com.android.settings.R;
55 import com.android.settingslib.animation.AppearAnimationUtils;
56 import com.android.settingslib.animation.DisappearAnimationUtils;
57 
58 import com.google.android.setupdesign.GlifLayout;
59 
60 import java.util.ArrayList;
61 
62 public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
63 
64     // The index of the array is isStrongAuth << 2 + isManagedProfile << 1 + isAlpha.
65     private static final int[] DETAIL_TEXTS = new int[] {
66         R.string.lockpassword_confirm_your_pin_generic,
67         R.string.lockpassword_confirm_your_password_generic,
68         R.string.lockpassword_confirm_your_pin_generic_profile,
69         R.string.lockpassword_confirm_your_password_generic_profile,
70         R.string.lockpassword_strong_auth_required_device_pin,
71         R.string.lockpassword_strong_auth_required_device_password,
72         R.string.lockpassword_strong_auth_required_work_pin,
73         R.string.lockpassword_strong_auth_required_work_password
74     };
75 
76     public static class InternalActivity extends ConfirmLockPassword {
77     }
78 
79     @Override
getIntent()80     public Intent getIntent() {
81         Intent modIntent = new Intent(super.getIntent());
82         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPasswordFragment.class.getName());
83         return modIntent;
84     }
85 
86     @Override
isValidFragment(String fragmentName)87     protected boolean isValidFragment(String fragmentName) {
88         if (ConfirmLockPasswordFragment.class.getName().equals(fragmentName)) return true;
89         return false;
90     }
91 
92     @Override
onWindowFocusChanged(boolean hasFocus)93     public void onWindowFocusChanged(boolean hasFocus) {
94         super.onWindowFocusChanged(hasFocus);
95         Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_content);
96         if (fragment != null && fragment instanceof ConfirmLockPasswordFragment) {
97             ((ConfirmLockPasswordFragment)fragment).onWindowFocusChanged(hasFocus);
98         }
99     }
100 
101     public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
102             implements OnClickListener, OnEditorActionListener,
103             CredentialCheckResultTracker.Listener {
104         private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
105         private ImeAwareEditText mPasswordEntry;
106         private TextViewInputDisabler mPasswordEntryInputDisabler;
107         private AsyncTask<?, ?, ?> mPendingLockCheck;
108         private CredentialCheckResultTracker mCredentialCheckResultTracker;
109         private boolean mDisappearing = false;
110         private CountDownTimer mCountdownTimer;
111         private boolean mIsAlpha;
112         private InputMethodManager mImm;
113         private AppearAnimationUtils mAppearAnimationUtils;
114         private DisappearAnimationUtils mDisappearAnimationUtils;
115         private boolean mIsManagedProfile;
116         private GlifLayout mGlifLayout;
117 
118         // required constructor for fragments
ConfirmLockPasswordFragment()119         public ConfirmLockPasswordFragment() {
120 
121         }
122 
123         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)124         public View onCreateView(LayoutInflater inflater, ViewGroup container,
125                 Bundle savedInstanceState) {
126             final int storedQuality = mLockPatternUtils.getKeyguardStoredPasswordQuality(
127                     mEffectiveUserId);
128 
129             ConfirmLockPassword activity = (ConfirmLockPassword) getActivity();
130             View view = inflater.inflate(
131                     activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.NORMAL
132                             ? R.layout.confirm_lock_password_normal
133                             : R.layout.confirm_lock_password,
134                     container,
135                     false);
136             mGlifLayout = view.findViewById(R.id.setup_wizard_layout);
137             mPasswordEntry = (ImeAwareEditText) view.findViewById(R.id.password_entry);
138             mPasswordEntry.setOnEditorActionListener(this);
139             // EditText inside ScrollView doesn't automatically get focus.
140             mPasswordEntry.requestFocus();
141             mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
142             mErrorTextView = (TextView) view.findViewById(R.id.errorText);
143             mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality
144                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality
145                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality
146                     || DevicePolicyManager.PASSWORD_QUALITY_MANAGED == storedQuality;
147 
148             mImm = (InputMethodManager) getActivity().getSystemService(
149                     Context.INPUT_METHOD_SERVICE);
150 
151             mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId);
152 
153             Intent intent = getActivity().getIntent();
154             if (intent != null) {
155                 CharSequence headerMessage = intent.getCharSequenceExtra(
156                         ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
157                 CharSequence detailsMessage = intent.getCharSequenceExtra(
158                         ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
159                 if (TextUtils.isEmpty(headerMessage) && mIsManagedProfile) {
160                     headerMessage = mDevicePolicyManager.getOrganizationNameForUser(mUserId);
161                 }
162                 if (TextUtils.isEmpty(headerMessage)) {
163                     headerMessage = getString(getDefaultHeader());
164                 }
165                 if (TextUtils.isEmpty(detailsMessage)) {
166                     detailsMessage = getString(getDefaultDetails());
167                 }
168                 mGlifLayout.setHeaderText(headerMessage);
169                 mGlifLayout.setDescriptionText(detailsMessage);
170             }
171             int currentType = mPasswordEntry.getInputType();
172             if (mIsAlpha) {
173                 mPasswordEntry.setInputType(currentType);
174                 mPasswordEntry.setContentDescription(
175                         getContext().getString(R.string.unlock_set_unlock_password_title));
176             } else {
177                 mPasswordEntry.setInputType(
178                         InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
179                 mPasswordEntry.setContentDescription(
180                         getContext().getString(R.string.unlock_set_unlock_pin_title));
181             }
182             // Can't set via XML since setInputType resets the fontFamily to null
183             mPasswordEntry.setTypeface(Typeface.create(
184                     getContext().getString(com.android.internal.R.string.config_headlineFontFamily),
185                     Typeface.NORMAL));
186             mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
187                     220, 2f /* translationScale */, 1f /* delayScale*/,
188                     AnimationUtils.loadInterpolator(getContext(),
189                             android.R.interpolator.linear_out_slow_in));
190             mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
191                     110, 1f /* translationScale */,
192                     0.5f /* delayScale */, AnimationUtils.loadInterpolator(
193                             getContext(), android.R.interpolator.fast_out_linear_in));
194             setAccessibilityTitle(mGlifLayout.getHeaderText());
195 
196             mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
197                     .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
198             if (mCredentialCheckResultTracker == null) {
199                 mCredentialCheckResultTracker = new CredentialCheckResultTracker();
200                 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
201                         FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
202             }
203 
204             return view;
205         }
206 
207         @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)208         public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
209             super.onViewCreated(view, savedInstanceState);
210             if (mForgotButton != null) {
211                 mForgotButton.setText(mIsAlpha
212                         ? R.string.lockpassword_forgot_password
213                         : R.string.lockpassword_forgot_pin);
214             }
215         }
216 
217         @Override
onDestroy()218         public void onDestroy() {
219             super.onDestroy();
220             mPasswordEntry.setText(null);
221             // Force a garbage collection to remove remnant of user password shards from memory.
222             // Execute this with a slight delay to allow the activity lifecycle to complete and
223             // the instance to become gc-able.
224             new Handler(Looper.myLooper()).postDelayed(() -> {
225                 System.gc();
226                 System.runFinalization();
227                 System.gc();
228             }, 5000);
229         }
230 
getDefaultHeader()231         private int getDefaultHeader() {
232             if (mFrp) {
233                 return mIsAlpha ? R.string.lockpassword_confirm_your_password_header_frp
234                         : R.string.lockpassword_confirm_your_pin_header_frp;
235             }
236             if (mIsManagedProfile) {
237                 return mIsAlpha ? R.string.lockpassword_confirm_your_work_password_header
238                         : R.string.lockpassword_confirm_your_work_pin_header;
239             }
240             return mIsAlpha ? R.string.lockpassword_confirm_your_password_header
241                     : R.string.lockpassword_confirm_your_pin_header;
242         }
243 
getDefaultDetails()244         private int getDefaultDetails() {
245             if (mFrp) {
246                 return mIsAlpha ? R.string.lockpassword_confirm_your_password_details_frp
247                         : R.string.lockpassword_confirm_your_pin_details_frp;
248             }
249             boolean isStrongAuthRequired = isStrongAuthRequired();
250             // Map boolean flags to an index by isStrongAuth << 2 + isManagedProfile << 1 + isAlpha.
251             int index = ((isStrongAuthRequired ? 1 : 0) << 2) + ((mIsManagedProfile ? 1 : 0) << 1)
252                     + (mIsAlpha ? 1 : 0);
253             return DETAIL_TEXTS[index];
254         }
255 
getErrorMessage()256         private int getErrorMessage() {
257             return mIsAlpha ? R.string.lockpassword_invalid_password
258                     : R.string.lockpassword_invalid_pin;
259         }
260 
261         @Override
getLastTryErrorMessage(int userType)262         protected int getLastTryErrorMessage(int userType) {
263             switch (userType) {
264                 case USER_TYPE_PRIMARY:
265                     return mIsAlpha ? R.string.lock_last_password_attempt_before_wipe_device
266                             : R.string.lock_last_pin_attempt_before_wipe_device;
267                 case USER_TYPE_MANAGED_PROFILE:
268                     return mIsAlpha ? R.string.lock_last_password_attempt_before_wipe_profile
269                             : R.string.lock_last_pin_attempt_before_wipe_profile;
270                 case USER_TYPE_SECONDARY:
271                     return mIsAlpha ? R.string.lock_last_password_attempt_before_wipe_user
272                             : R.string.lock_last_pin_attempt_before_wipe_user;
273                 default:
274                     throw new IllegalArgumentException("Unrecognized user type:" + userType);
275             }
276         }
277 
278         @Override
prepareEnterAnimation()279         public void prepareEnterAnimation() {
280             super.prepareEnterAnimation();
281             mGlifLayout.getHeaderTextView().setAlpha(0f);
282             mGlifLayout.getDescriptionTextView().setAlpha(0f);
283             mCancelButton.setAlpha(0f);
284             if (mForgotButton != null) {
285                 mForgotButton.setAlpha(0f);
286             }
287             mPasswordEntry.setAlpha(0f);
288             mErrorTextView.setAlpha(0f);
289         }
290 
getActiveViews()291         private View[] getActiveViews() {
292             ArrayList<View> result = new ArrayList<>();
293             result.add(mGlifLayout.getHeaderTextView());
294             result.add(mGlifLayout.getDescriptionTextView());
295             if (mCancelButton.getVisibility() == View.VISIBLE) {
296                 result.add(mCancelButton);
297             }
298             if (mForgotButton != null) {
299                 result.add(mForgotButton);
300             }
301             result.add(mPasswordEntry);
302             result.add(mErrorTextView);
303             return result.toArray(new View[] {});
304         }
305 
306         @Override
startEnterAnimation()307         public void startEnterAnimation() {
308             super.startEnterAnimation();
309             mAppearAnimationUtils.startAnimation(getActiveViews(), this::updatePasswordEntry);
310         }
311 
312         @Override
onPause()313         public void onPause() {
314             super.onPause();
315             if (mCountdownTimer != null) {
316                 mCountdownTimer.cancel();
317                 mCountdownTimer = null;
318             }
319             mCredentialCheckResultTracker.setListener(null);
320         }
321 
322         @Override
getMetricsCategory()323         public int getMetricsCategory() {
324             return SettingsEnums.CONFIRM_LOCK_PASSWORD;
325         }
326 
327         @Override
onResume()328         public void onResume() {
329             super.onResume();
330             long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
331             if (deadline != 0) {
332                 mCredentialCheckResultTracker.clearResult();
333                 handleAttemptLockout(deadline);
334             } else {
335                 updatePasswordEntry();
336                 mErrorTextView.setText("");
337                 updateErrorMessage(
338                         mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
339             }
340             mCredentialCheckResultTracker.setListener(this);
341         }
342 
343         @Override
authenticationSucceeded()344         protected void authenticationSucceeded() {
345             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
346         }
347 
updatePasswordEntry()348         private void updatePasswordEntry() {
349             final boolean isLockedOut =
350                     mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0;
351             mPasswordEntry.setEnabled(!isLockedOut);
352             mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut);
353             if (isLockedOut) {
354                 mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/);
355             } else {
356                 mPasswordEntry.scheduleShowSoftInput();
357             }
358         }
359 
onWindowFocusChanged(boolean hasFocus)360         public void onWindowFocusChanged(boolean hasFocus) {
361             if (!hasFocus) {
362                 return;
363             }
364             // Post to let window focus logic to finish to allow soft input show/hide properly.
365             mPasswordEntry.post(this::updatePasswordEntry);
366         }
367 
handleNext()368         private void handleNext() {
369             if (mPendingLockCheck != null || mDisappearing) {
370                 return;
371             }
372 
373             // TODO(b/120484642): This is a point of entry for passwords from the UI
374             final Editable passwordText = mPasswordEntry.getText();
375             if (TextUtils.isEmpty(passwordText)) {
376                 return;
377             }
378             final LockscreenCredential credential =
379                     mIsAlpha ? LockscreenCredential.createPassword(passwordText)
380                     : LockscreenCredential.createPin(passwordText);
381 
382             mPasswordEntryInputDisabler.setInputEnabled(false);
383 
384             Intent intent = new Intent();
385             // TODO(b/161956762): Sanitize this
386             if (mReturnGatekeeperPassword) {
387                 if (isInternalActivity()) {
388                     startVerifyPassword(credential, intent,
389                             LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE);
390                     return;
391                 }
392             } else if (mForceVerifyPath)  {
393                 if (isInternalActivity()) {
394                     startVerifyPassword(credential, intent, 0 /* flags */);
395                     return;
396                 }
397             } else {
398                 startCheckPassword(credential, intent);
399                 return;
400             }
401 
402             mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
403         }
404 
isInternalActivity()405         private boolean isInternalActivity() {
406             return getActivity() instanceof ConfirmLockPassword.InternalActivity;
407         }
408 
startVerifyPassword(LockscreenCredential credential, final Intent intent, @LockPatternUtils.VerifyFlag int flags)409         private void startVerifyPassword(LockscreenCredential credential, final Intent intent,
410                 @LockPatternUtils.VerifyFlag int flags) {
411             final int localEffectiveUserId = mEffectiveUserId;
412             final int localUserId = mUserId;
413             final LockPatternChecker.OnVerifyCallback onVerifyCallback = (response, timeoutMs) -> {
414                 mPendingLockCheck = null;
415                 final boolean matched = response.isMatched();
416                 if (matched && mReturnCredentials) {
417                     if ((flags & LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0) {
418                         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
419                                 response.getGatekeeperPasswordHandle());
420                     } else {
421                         intent.putExtra(
422                                 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
423                                 response.getGatekeeperHAT());
424                     }
425                 }
426                 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
427                         localEffectiveUserId);
428             };
429             mPendingLockCheck = (localEffectiveUserId == localUserId)
430                     ? LockPatternChecker.verifyCredential(mLockPatternUtils, credential,
431                             localUserId, flags, onVerifyCallback)
432                     : LockPatternChecker.verifyTiedProfileChallenge(mLockPatternUtils, credential,
433                             localUserId, flags, onVerifyCallback);
434         }
435 
startCheckPassword(final LockscreenCredential credential, final Intent intent)436         private void startCheckPassword(final LockscreenCredential credential,
437                 final Intent intent) {
438             final int localEffectiveUserId = mEffectiveUserId;
439             mPendingLockCheck = LockPatternChecker.checkCredential(
440                     mLockPatternUtils,
441                     credential,
442                     localEffectiveUserId,
443                     new LockPatternChecker.OnCheckCallback() {
444                         @Override
445                         public void onChecked(boolean matched, int timeoutMs) {
446                             mPendingLockCheck = null;
447                             if (matched && isInternalActivity() && mReturnCredentials) {
448                                 // TODO: get rid of EXTRA_KEY_TYPE, since EXTRA_KEY_PASSWORD already
449                                 // distinguishes beteween PIN and password.
450                                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
451                                                 mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD
452                                                          : StorageManager.CRYPT_TYPE_PIN);
453                                 intent.putExtra(
454                                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, credential);
455                             }
456                             mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
457                                     localEffectiveUserId);
458                         }
459                     });
460         }
461 
startDisappearAnimation(final Intent intent)462         private void startDisappearAnimation(final Intent intent) {
463             if (mDisappearing) {
464                 return;
465             }
466             mDisappearing = true;
467 
468             final ConfirmLockPassword activity = (ConfirmLockPassword) getActivity();
469             // Bail if there is no active activity.
470             if (activity == null || activity.isFinishing()) {
471                 return;
472             }
473             if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) {
474                 mDisappearAnimationUtils.startAnimation(getActiveViews(), () -> {
475                     activity.setResult(RESULT_OK, intent);
476                     activity.finish();
477                     activity.overridePendingTransition(
478                             R.anim.confirm_credential_close_enter,
479                             R.anim.confirm_credential_close_exit);
480                 });
481             } else {
482                 activity.setResult(RESULT_OK, intent);
483                 activity.finish();
484             }
485         }
486 
onPasswordChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)487         private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs,
488                 int effectiveUserId, boolean newResult) {
489             mPasswordEntryInputDisabler.setInputEnabled(true);
490             if (matched) {
491                 if (newResult) {
492                     ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
493                             mUserManager, mDevicePolicyManager, mEffectiveUserId,
494                             /* isStrongAuth */ true);
495                 }
496                 startDisappearAnimation(intent);
497                 ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
498             } else {
499                 if (timeoutMs > 0) {
500                     refreshLockScreen();
501                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
502                             effectiveUserId, timeoutMs);
503                     handleAttemptLockout(deadline);
504                 } else {
505                     showError(getErrorMessage(), CLEAR_WRONG_ATTEMPT_TIMEOUT_MS);
506                 }
507                 if (newResult) {
508                     reportFailedAttempt();
509                 }
510             }
511         }
512 
513         @Override
onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)514         public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
515                 int effectiveUserId, boolean newResult) {
516             onPasswordChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
517         }
518 
519         @Override
onShowError()520         protected void onShowError() {
521             mPasswordEntry.setText(null);
522         }
523 
handleAttemptLockout(long elapsedRealtimeDeadline)524         private void handleAttemptLockout(long elapsedRealtimeDeadline) {
525             mCountdownTimer = new CountDownTimer(
526                     elapsedRealtimeDeadline - SystemClock.elapsedRealtime(),
527                     LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
528 
529                 @Override
530                 public void onTick(long millisUntilFinished) {
531                     final int secondsCountdown = (int) (millisUntilFinished / 1000);
532                     showError(getString(
533                             R.string.lockpattern_too_many_failed_confirmation_attempts,
534                             secondsCountdown), 0);
535                 }
536 
537                 @Override
538                 public void onFinish() {
539                     updatePasswordEntry();
540                     mErrorTextView.setText("");
541                     updateErrorMessage(
542                             mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
543                 }
544             }.start();
545             updatePasswordEntry();
546         }
547 
onClick(View v)548         public void onClick(View v) {
549             if (v.getId() == R.id.next_button) {
550                 handleNext();
551             } else if (v.getId() == R.id.cancel_button) {
552                 getActivity().setResult(RESULT_CANCELED);
553                 getActivity().finish();
554             }
555         }
556 
557         // {@link OnEditorActionListener} methods.
onEditorAction(TextView v, int actionId, KeyEvent event)558         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
559             // Check if this was the result of hitting the enter or "done" key
560             if (actionId == EditorInfo.IME_NULL
561                     || actionId == EditorInfo.IME_ACTION_DONE
562                     || actionId == EditorInfo.IME_ACTION_NEXT) {
563                 handleNext();
564                 return true;
565             }
566             return false;
567         }
568     }
569 }
570