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.email.activity.setup; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.app.DialogFragment; 23 import android.app.FragmentManager; 24 import android.app.LoaderManager; 25 import android.app.admin.DevicePolicyManager; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.Intent; 29 import android.content.Loader; 30 import android.content.res.Resources; 31 import android.net.Uri; 32 import android.os.AsyncTask; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.text.TextUtils; 36 37 import com.android.email.DebugUtils; 38 import com.android.email.R; 39 import com.android.email.SecurityPolicy; 40 import com.android.emailcommon.provider.Account; 41 import com.android.emailcommon.provider.EmailContent; 42 import com.android.emailcommon.provider.HostAuth; 43 import com.android.emailcommon.provider.Policy; 44 import com.android.emailcommon.utility.IntentUtilities; 45 import com.android.mail.ui.MailAsyncTaskLoader; 46 import com.android.mail.utils.LogUtils; 47 48 /** 49 * Psuedo-activity (no UI) to bootstrap the user up to a higher desired security level. This 50 * bootstrap requires the following steps. 51 * 52 * 1. Confirm the account of interest has any security policies defined - exit early if not 53 * 2. If not actively administrating the device, ask Device Policy Manager to start that 54 * 3. When we are actively administrating, check current policies and see if they're sufficient 55 * 4. If not, set policies 56 * 5. If necessary, request for user to update device password 57 * 6. If necessary, request for user to activate device encryption 58 */ 59 public class AccountSecurity extends Activity { 60 private static final String TAG = "Email/AccountSecurity"; 61 62 private static final boolean DEBUG = false; // Don't ship with this set to true 63 64 private static final String EXTRA_ACCOUNT_ID = "ACCOUNT_ID"; 65 private static final String EXTRA_SHOW_DIALOG = "SHOW_DIALOG"; 66 private static final String EXTRA_PASSWORD_EXPIRING = "EXPIRING"; 67 private static final String EXTRA_PASSWORD_EXPIRED = "EXPIRED"; 68 69 private static final String SAVESTATE_INITIALIZED_TAG = "initialized"; 70 private static final String SAVESTATE_TRIED_ADD_ADMINISTRATOR_TAG = "triedAddAdministrator"; 71 private static final String SAVESTATE_TRIED_SET_PASSWORD_TAG = "triedSetpassword"; 72 private static final String SAVESTATE_TRIED_SET_ENCRYPTION_TAG = "triedSetEncryption"; 73 private static final String SAVESTATE_ACCOUNT_TAG = "account"; 74 75 private static final int REQUEST_ENABLE = 1; 76 private static final int REQUEST_PASSWORD = 2; 77 private static final int REQUEST_ENCRYPTION = 3; 78 79 private boolean mTriedAddAdministrator; 80 private boolean mTriedSetPassword; 81 private boolean mTriedSetEncryption; 82 83 private Account mAccount; 84 85 protected boolean mInitialized; 86 87 private Handler mHandler; 88 private boolean mActivityResumed; 89 90 private static final int ACCOUNT_POLICY_LOADER_ID = 0; 91 private AccountAndPolicyLoaderCallbacks mAPLoaderCallbacks; 92 private Bundle mAPLoaderArgs; 93 94 public static Uri getUpdateSecurityUri(final long accountId, final boolean showDialog) { 95 final Uri.Builder baseUri = Uri.parse("auth://" + EmailContent.EMAIL_PACKAGE_NAME + 96 ".ACCOUNT_SECURITY/").buildUpon(); 97 IntentUtilities.setAccountId(baseUri, accountId); 98 baseUri.appendQueryParameter(EXTRA_SHOW_DIALOG, Boolean.toString(showDialog)); 99 return baseUri.build(); 100 } 101 102 /** 103 * Used for generating intent for this activity (which is intended to be launched 104 * from a notification.) 105 * 106 * @param context Calling context for building the intent 107 * @param accountId The account of interest 108 * @param showDialog If true, a simple warning dialog will be shown before kicking off 109 * the necessary system settings. Should be true anywhere the context of the security settings 110 * is not clear (e.g. any time after the account has been set up). 111 * @return an Intent which can be used to view that account 112 */ 113 public static Intent actionUpdateSecurityIntent(Context context, long accountId, 114 boolean showDialog) { 115 Intent intent = new Intent(context, AccountSecurity.class); 116 intent.putExtra(EXTRA_ACCOUNT_ID, accountId); 117 intent.putExtra(EXTRA_SHOW_DIALOG, showDialog); 118 return intent; 119 } 120 121 /** 122 * Used for generating intent for this activity (which is intended to be launched 123 * from a notification.) This is a special mode of this activity which exists only 124 * to give the user a dialog (for context) about a device pin/password expiration event. 125 */ 126 public static Intent actionDevicePasswordExpirationIntent(Context context, long accountId, 127 boolean expired) { 128 Intent intent = new ForwardingIntent(context, AccountSecurity.class); 129 intent.putExtra(EXTRA_ACCOUNT_ID, accountId); 130 intent.putExtra(expired ? EXTRA_PASSWORD_EXPIRED : EXTRA_PASSWORD_EXPIRING, true); 131 return intent; 132 } 133 134 @Override 135 public void onCreate(Bundle savedInstanceState) { 136 super.onCreate(savedInstanceState); 137 138 mHandler = new Handler(); 139 140 final Intent i = getIntent(); 141 final long accountId; 142 Bundle extras = i.getExtras(); 143 if (extras == null) { 144 // We have been invoked via a uri. We need to get our parameters from the URI instead 145 // of looking in the intent extras. 146 extras = new Bundle(); 147 accountId = IntentUtilities.getAccountIdFromIntent(i); 148 extras.putLong(EXTRA_ACCOUNT_ID, accountId); 149 boolean showDialog = false; 150 final String value = i.getData().getQueryParameter(EXTRA_SHOW_DIALOG); 151 if (!TextUtils.isEmpty(value)) { 152 showDialog = Boolean.getBoolean(value); 153 } 154 extras.putBoolean(EXTRA_SHOW_DIALOG, showDialog); 155 } else { 156 accountId = i.getLongExtra(EXTRA_ACCOUNT_ID, -1); 157 extras = i.getExtras(); 158 } 159 160 final SecurityPolicy security = SecurityPolicy.getInstance(this); 161 security.clearNotification(); 162 if (accountId == -1) { 163 finish(); 164 return; 165 } 166 167 if (savedInstanceState != null) { 168 mInitialized = savedInstanceState.getBoolean(SAVESTATE_INITIALIZED_TAG, false); 169 170 mTriedAddAdministrator = 171 savedInstanceState.getBoolean(SAVESTATE_TRIED_ADD_ADMINISTRATOR_TAG, false); 172 mTriedSetPassword = 173 savedInstanceState.getBoolean(SAVESTATE_TRIED_SET_PASSWORD_TAG, false); 174 mTriedSetEncryption = 175 savedInstanceState.getBoolean(SAVESTATE_TRIED_SET_ENCRYPTION_TAG, false); 176 177 mAccount = savedInstanceState.getParcelable(SAVESTATE_ACCOUNT_TAG); 178 } 179 180 if (!mInitialized) { 181 startAccountAndPolicyLoader(extras); 182 } 183 } 184 185 @Override 186 protected void onSaveInstanceState(final Bundle outState) { 187 super.onSaveInstanceState(outState); 188 outState.putBoolean(SAVESTATE_INITIALIZED_TAG, mInitialized); 189 190 outState.putBoolean(SAVESTATE_TRIED_ADD_ADMINISTRATOR_TAG, mTriedAddAdministrator); 191 outState.putBoolean(SAVESTATE_TRIED_SET_PASSWORD_TAG, mTriedSetPassword); 192 outState.putBoolean(SAVESTATE_TRIED_SET_ENCRYPTION_TAG, mTriedSetEncryption); 193 194 outState.putParcelable(SAVESTATE_ACCOUNT_TAG, mAccount); 195 } 196 197 @Override 198 protected void onPause() { 199 super.onPause(); 200 mActivityResumed = false; 201 } 202 203 @Override 204 protected void onResume() { 205 super.onResume(); 206 mActivityResumed = true; 207 tickleAccountAndPolicyLoader(); 208 } 209 210 protected boolean isActivityResumed() { 211 return mActivityResumed; 212 } 213 214 private void tickleAccountAndPolicyLoader() { 215 // If we're already initialized we don't need to tickle. 216 if (!mInitialized) { 217 getLoaderManager().initLoader(ACCOUNT_POLICY_LOADER_ID, mAPLoaderArgs, 218 mAPLoaderCallbacks); 219 } 220 } 221 222 private void startAccountAndPolicyLoader(final Bundle args) { 223 mAPLoaderArgs = args; 224 mAPLoaderCallbacks = new AccountAndPolicyLoaderCallbacks(); 225 tickleAccountAndPolicyLoader(); 226 } 227 228 private class AccountAndPolicyLoaderCallbacks 229 implements LoaderManager.LoaderCallbacks<Account> { 230 @Override 231 public Loader<Account> onCreateLoader(final int id, final Bundle args) { 232 final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1); 233 final boolean showDialog = args.getBoolean(EXTRA_SHOW_DIALOG, false); 234 final boolean passwordExpiring = 235 args.getBoolean(EXTRA_PASSWORD_EXPIRING, false); 236 final boolean passwordExpired = 237 args.getBoolean(EXTRA_PASSWORD_EXPIRED, false); 238 239 return new AccountAndPolicyLoader(getApplicationContext(), accountId, 240 showDialog, passwordExpiring, passwordExpired); 241 } 242 243 @Override 244 public void onLoadFinished(final Loader<Account> loader, final Account account) { 245 mHandler.post(new Runnable() { 246 @Override 247 public void run() { 248 final AccountSecurity activity = AccountSecurity.this; 249 if (!activity.isActivityResumed()) { 250 return; 251 } 252 253 if (account == null || (account.mPolicyKey != 0 && account.mPolicy == null)) { 254 activity.finish(); 255 LogUtils.d(TAG, "could not load account or policy in AccountSecurity"); 256 return; 257 } 258 259 if (!activity.mInitialized) { 260 activity.mInitialized = true; 261 262 final AccountAndPolicyLoader apLoader = (AccountAndPolicyLoader) loader; 263 activity.completeCreate(account, apLoader.mShowDialog, 264 apLoader.mPasswordExpiring, apLoader.mPasswordExpired); 265 } 266 } 267 }); 268 } 269 270 @Override 271 public void onLoaderReset(Loader<Account> loader) {} 272 } 273 274 private static class AccountAndPolicyLoader extends MailAsyncTaskLoader<Account> { 275 private final long mAccountId; 276 public final boolean mShowDialog; 277 public final boolean mPasswordExpiring; 278 public final boolean mPasswordExpired; 279 280 private final Context mContext; 281 282 AccountAndPolicyLoader(final Context context, final long accountId, 283 final boolean showDialog, final boolean passwordExpiring, 284 final boolean passwordExpired) { 285 super(context); 286 mContext = context; 287 mAccountId = accountId; 288 mShowDialog = showDialog; 289 mPasswordExpiring = passwordExpiring; 290 mPasswordExpired = passwordExpired; 291 } 292 293 @Override 294 public Account loadInBackground() { 295 final Account account = Account.restoreAccountWithId(mContext, mAccountId); 296 if (account == null) { 297 return null; 298 } 299 300 final long policyId = account.mPolicyKey; 301 if (policyId != 0) { 302 account.mPolicy = Policy.restorePolicyWithId(mContext, policyId); 303 } 304 305 account.getOrCreateHostAuthRecv(mContext); 306 307 return account; 308 } 309 310 @Override 311 protected void onDiscardResult(Account result) {} 312 } 313 314 protected void completeCreate(final Account account, final boolean showDialog, 315 final boolean passwordExpiring, final boolean passwordExpired) { 316 mAccount = account; 317 318 // Special handling for password expiration events 319 if (passwordExpiring || passwordExpired) { 320 FragmentManager fm = getFragmentManager(); 321 if (fm.findFragmentByTag("password_expiration") == null) { 322 PasswordExpirationDialog dialog = 323 PasswordExpirationDialog.newInstance(mAccount.getDisplayName(), 324 passwordExpired); 325 if (DebugUtils.DEBUG || DEBUG) { 326 LogUtils.d(TAG, "Showing password expiration dialog"); 327 } 328 dialog.show(fm, "password_expiration"); 329 } 330 return; 331 } 332 // Otherwise, handle normal security settings flow 333 if (mAccount.mPolicyKey != 0) { 334 // This account wants to control security 335 if (showDialog) { 336 // Show dialog first, unless already showing (e.g. after rotation) 337 FragmentManager fm = getFragmentManager(); 338 if (fm.findFragmentByTag("security_needed") == null) { 339 SecurityNeededDialog dialog = 340 SecurityNeededDialog.newInstance(mAccount.getDisplayName()); 341 if (DebugUtils.DEBUG || DEBUG) { 342 LogUtils.d(TAG, "Showing security needed dialog"); 343 } 344 dialog.show(fm, "security_needed"); 345 } 346 } else { 347 // Go directly to security settings 348 tryAdvanceSecurity(mAccount); 349 } 350 return; 351 } 352 finish(); 353 } 354 355 /** 356 * After any of the activities return, try to advance to the "next step" 357 */ 358 @Override 359 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 360 tryAdvanceSecurity(mAccount); 361 super.onActivityResult(requestCode, resultCode, data); 362 } 363 364 /** 365 * Walk the user through the required steps to become an active administrator and with 366 * the requisite security settings for the given account. 367 * 368 * These steps will be repeated each time we return from a given attempt (e.g. asking the 369 * user to choose a device pin/password). In a typical activation, we may repeat these 370 * steps a few times. It may go as far as step 5 (password) or step 6 (encryption), but it 371 * will terminate when step 2 (isActive()) succeeds. 372 * 373 * If at any point we do not advance beyond a given user step, (e.g. the user cancels 374 * instead of setting a password) we simply repost the security notification, and exit. 375 * We never want to loop here. 376 */ 377 private void tryAdvanceSecurity(Account account) { 378 SecurityPolicy security = SecurityPolicy.getInstance(this); 379 // Step 1. Check if we are an active device administrator, and stop here to activate 380 if (!security.isActiveAdmin()) { 381 if (mTriedAddAdministrator) { 382 if (DebugUtils.DEBUG || DEBUG) { 383 LogUtils.d(TAG, "Not active admin: repost notification"); 384 } 385 repostNotification(account, security); 386 finish(); 387 } else { 388 mTriedAddAdministrator = true; 389 // retrieve name of server for the format string 390 final HostAuth hostAuth = account.mHostAuthRecv; 391 if (hostAuth == null) { 392 if (DebugUtils.DEBUG || DEBUG) { 393 LogUtils.d(TAG, "No HostAuth: repost notification"); 394 } 395 repostNotification(account, security); 396 finish(); 397 } else { 398 if (DebugUtils.DEBUG || DEBUG) { 399 LogUtils.d(TAG, "Not active admin: post initial notification"); 400 } 401 // try to become active - must happen here in activity, to get result 402 Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); 403 intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, 404 security.getAdminComponent()); 405 intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, 406 this.getString(R.string.account_security_policy_explanation_fmt, 407 hostAuth.mAddress)); 408 startActivityForResult(intent, REQUEST_ENABLE); 409 } 410 } 411 return; 412 } 413 414 // Step 2. Check if the current aggregate security policy is being satisfied by the 415 // DevicePolicyManager (the current system security level). 416 if (security.isActive(null)) { 417 if (DebugUtils.DEBUG || DEBUG) { 418 LogUtils.d(TAG, "Security active; clear holds"); 419 } 420 Account.clearSecurityHoldOnAllAccounts(this); 421 security.syncAccount(account); 422 security.clearNotification(); 423 finish(); 424 return; 425 } 426 427 // Step 3. Try to assert the current aggregate security requirements with the system. 428 security.setActivePolicies(); 429 430 // Step 4. Recheck the security policy, and determine what changes are needed (if any) 431 // to satisfy the requirements. 432 int inactiveReasons = security.getInactiveReasons(null); 433 434 // Step 5. If password is needed, try to have the user set it 435 if ((inactiveReasons & SecurityPolicy.INACTIVE_NEED_PASSWORD) != 0) { 436 if (mTriedSetPassword) { 437 if (DebugUtils.DEBUG || DEBUG) { 438 LogUtils.d(TAG, "Password needed; repost notification"); 439 } 440 repostNotification(account, security); 441 finish(); 442 } else { 443 if (DebugUtils.DEBUG || DEBUG) { 444 LogUtils.d(TAG, "Password needed; request it via DPM"); 445 } 446 mTriedSetPassword = true; 447 // launch the activity to have the user set a new password. 448 Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD); 449 startActivityForResult(intent, REQUEST_PASSWORD); 450 } 451 return; 452 } 453 454 // Step 6. If encryption is needed, try to have the user set it 455 if ((inactiveReasons & SecurityPolicy.INACTIVE_NEED_ENCRYPTION) != 0) { 456 if (mTriedSetEncryption) { 457 if (DebugUtils.DEBUG || DEBUG) { 458 LogUtils.d(TAG, "Encryption needed; repost notification"); 459 } 460 repostNotification(account, security); 461 finish(); 462 } else { 463 if (DebugUtils.DEBUG || DEBUG) { 464 LogUtils.d(TAG, "Encryption needed; request it via DPM"); 465 } 466 mTriedSetEncryption = true; 467 // launch the activity to start up encryption. 468 Intent intent = new Intent(DevicePolicyManager.ACTION_START_ENCRYPTION); 469 startActivityForResult(intent, REQUEST_ENCRYPTION); 470 } 471 return; 472 } 473 474 // Step 7. No problems were found, so clear holds and exit 475 if (DebugUtils.DEBUG || DEBUG) { 476 LogUtils.d(TAG, "Policies enforced; clear holds"); 477 } 478 Account.clearSecurityHoldOnAllAccounts(this); 479 security.syncAccount(account); 480 security.clearNotification(); 481 finish(); 482 } 483 484 /** 485 * Mark an account as not-ready-for-sync and post a notification to bring the user back here 486 * eventually. 487 */ 488 private static void repostNotification(final Account account, final SecurityPolicy security) { 489 if (account == null) return; 490 new AsyncTask<Void, Void, Void>() { 491 @Override 492 protected Void doInBackground(Void... params) { 493 security.policiesRequired(account.mId); 494 return null; 495 } 496 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 497 } 498 499 /** 500 * Dialog briefly shown in some cases, to indicate the user that a security update is needed. 501 * If the user clicks OK, we proceed into the "tryAdvanceSecurity" flow. If the user cancels, 502 * we repost the notification and finish() the activity. 503 */ 504 public static class SecurityNeededDialog extends DialogFragment 505 implements DialogInterface.OnClickListener { 506 private static final String BUNDLE_KEY_ACCOUNT_NAME = "account_name"; 507 508 // Public no-args constructor needed for fragment re-instantiation 509 public SecurityNeededDialog() {} 510 511 /** 512 * Create a new dialog. 513 */ 514 public static SecurityNeededDialog newInstance(String accountName) { 515 final SecurityNeededDialog dialog = new SecurityNeededDialog(); 516 Bundle b = new Bundle(); 517 b.putString(BUNDLE_KEY_ACCOUNT_NAME, accountName); 518 dialog.setArguments(b); 519 return dialog; 520 } 521 522 @Override 523 public Dialog onCreateDialog(Bundle savedInstanceState) { 524 final String accountName = getArguments().getString(BUNDLE_KEY_ACCOUNT_NAME); 525 526 final Context context = getActivity(); 527 final Resources res = context.getResources(); 528 final AlertDialog.Builder b = new AlertDialog.Builder(context); 529 b.setTitle(R.string.account_security_dialog_title); 530 b.setIconAttribute(android.R.attr.alertDialogIcon); 531 b.setMessage(res.getString(R.string.account_security_dialog_content_fmt, accountName)); 532 b.setPositiveButton(android.R.string.ok, this); 533 b.setNegativeButton(android.R.string.cancel, this); 534 if (DebugUtils.DEBUG || DEBUG) { 535 LogUtils.d(TAG, "Posting security needed dialog"); 536 } 537 return b.create(); 538 } 539 540 @Override 541 public void onClick(DialogInterface dialog, int which) { 542 dismiss(); 543 AccountSecurity activity = (AccountSecurity) getActivity(); 544 if (activity.mAccount == null) { 545 // Clicked before activity fully restored - probably just monkey - exit quickly 546 activity.finish(); 547 return; 548 } 549 switch (which) { 550 case DialogInterface.BUTTON_POSITIVE: 551 if (DebugUtils.DEBUG || DEBUG) { 552 LogUtils.d(TAG, "User accepts; advance to next step"); 553 } 554 activity.tryAdvanceSecurity(activity.mAccount); 555 break; 556 case DialogInterface.BUTTON_NEGATIVE: 557 if (DebugUtils.DEBUG || DEBUG) { 558 LogUtils.d(TAG, "User declines; repost notification"); 559 } 560 AccountSecurity.repostNotification( 561 activity.mAccount, SecurityPolicy.getInstance(activity)); 562 activity.finish(); 563 break; 564 } 565 } 566 } 567 568 /** 569 * Dialog briefly shown in some cases, to indicate the user that the PIN/Password is expiring 570 * or has expired. If the user clicks OK, we launch the password settings screen. 571 */ 572 public static class PasswordExpirationDialog extends DialogFragment 573 implements DialogInterface.OnClickListener { 574 private static final String BUNDLE_KEY_ACCOUNT_NAME = "account_name"; 575 private static final String BUNDLE_KEY_EXPIRED = "expired"; 576 577 /** 578 * Create a new dialog. 579 */ 580 public static PasswordExpirationDialog newInstance(String accountName, boolean expired) { 581 final PasswordExpirationDialog dialog = new PasswordExpirationDialog(); 582 Bundle b = new Bundle(); 583 b.putString(BUNDLE_KEY_ACCOUNT_NAME, accountName); 584 b.putBoolean(BUNDLE_KEY_EXPIRED, expired); 585 dialog.setArguments(b); 586 return dialog; 587 } 588 589 // Public no-args constructor needed for fragment re-instantiation 590 public PasswordExpirationDialog() {} 591 592 /** 593 * Note, this actually creates two slightly different dialogs (for expiring vs. expired) 594 */ 595 @Override 596 public Dialog onCreateDialog(Bundle savedInstanceState) { 597 final String accountName = getArguments().getString(BUNDLE_KEY_ACCOUNT_NAME); 598 final boolean expired = getArguments().getBoolean(BUNDLE_KEY_EXPIRED); 599 final int titleId = expired 600 ? R.string.password_expired_dialog_title 601 : R.string.password_expire_warning_dialog_title; 602 final int contentId = expired 603 ? R.string.password_expired_dialog_content_fmt 604 : R.string.password_expire_warning_dialog_content_fmt; 605 606 final Context context = getActivity(); 607 final Resources res = context.getResources(); 608 return new AlertDialog.Builder(context) 609 .setTitle(titleId) 610 .setIconAttribute(android.R.attr.alertDialogIcon) 611 .setMessage(res.getString(contentId, accountName)) 612 .setPositiveButton(android.R.string.ok, this) 613 .setNegativeButton(android.R.string.cancel, this) 614 .create(); 615 } 616 617 @Override 618 public void onClick(DialogInterface dialog, int which) { 619 dismiss(); 620 AccountSecurity activity = (AccountSecurity) getActivity(); 621 if (which == DialogInterface.BUTTON_POSITIVE) { 622 Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD); 623 activity.startActivity(intent); 624 } 625 activity.finish(); 626 } 627 } 628 } 629