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