"Forgot my password" to start profile in locked state.

Currently if a work profile with a separate lock is turned off
(a.k.a. in quiet mode), and the user has forgotten the password,
profile owner app cannot use DPM.resetPasswordWithToken because
the profile user is not running.

In BYOD case the user can remove and re-provision the profile but
in the new COPE mode (a.k.a. on an organization owned device with
work profile) it is not possible to remove the profile. So full
factory reset is required.

This CL allows the user to start the profile in locked state
(a.k.a direct boot mode) so that the admin can reset the password.

This CL adds "Forgot my password" button to work profile credential prompt
if all of the following conditions are true:
 * Work profile is turned off
 * Profile owner app is capable of running in direct boot mode.
 * Profile owner app has an active password reset token.
 * The device is an FBE device (otherwise profile will be unlocked).

Clicking this button starts the profile in locked state and shows an
activity to the user that instruct them to go to their IT admin.

Bug: 143516540
Test: manual
Change-Id: I832f7121b43e39161c5afa816f44ce89584b66e2
This commit is contained in:
Pavel Grafov
2020-02-13 21:39:37 +00:00
parent f2a52c30a1
commit 04f783c759
12 changed files with 216 additions and 18 deletions

View File

@@ -1617,6 +1617,10 @@
android:windowSoftInputMode="stateHidden|adjustResize" android:windowSoftInputMode="stateHidden|adjustResize"
android:theme="@style/GlifTheme.Light"/> android:theme="@style/GlifTheme.Light"/>
<activity android:name=".password.ForgotPasswordActivity"
android:theme="@style/GlifV3Theme.Light"
android:exported="false"/>
<activity android:name=".biometrics.face.FaceEnrollIntroduction" <activity android:name=".biometrics.face.FaceEnrollIntroduction"
android:exported="false" android:exported="false"
android:screenOrientation="portrait"/> android:screenOrientation="portrait"/>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"
android:fillColor="?android:attr/colorPrimary"/>
</vector>

View File

@@ -54,6 +54,15 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginSides" android:layout_marginStart="?attr/sudMarginSides"
android:layout_marginEnd="?attr/sudMarginSides" /> android:layout_marginEnd="?attr/sudMarginSides" />
<Button
android:id="@+id/forgotButton"
style="@style/SudGlifButton.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginSides"
android:layout_marginEnd="?attr/sudMarginSides"
android:layout_gravity="center" />
</LinearLayout> </LinearLayout>
<Space <Space

View File

@@ -70,6 +70,15 @@
android:layout_marginEnd="?attr/sudMarginSides" android:layout_marginEnd="?attr/sudMarginSides"
android:text="@string/cancel" /> android:text="@string/cancel" />
<Button
android:id="@+id/forgotButton"
style="@style/SudGlifButton.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginSides"
android:layout_marginEnd="?attr/sudMarginSides"
android:layout_gravity="center" />
<Space <Space
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"

View File

@@ -53,6 +53,15 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginSides" android:layout_marginStart="?attr/sudMarginSides"
android:layout_marginEnd="?attr/sudMarginSides" /> android:layout_marginEnd="?attr/sudMarginSides" />
<Button
android:id="@+id/forgotButton"
style="@style/SudGlifButton.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginSides"
android:layout_marginEnd="?attr/sudMarginSides"
android:layout_gravity="center" />
</LinearLayout> </LinearLayout>
<Space <Space

View File

@@ -53,7 +53,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginSides" android:layout_marginStart="?attr/sudMarginSides"
android:layout_marginEnd="?attr/sudMarginSides" />x android:layout_marginEnd="?attr/sudMarginSides" />
<Button <Button
android:id="@+id/cancelButton" android:id="@+id/cancelButton"
@@ -65,6 +65,15 @@
android:layout_marginBottom="80dp" android:layout_marginBottom="80dp"
android:text="@string/cancel" /> android:text="@string/cancel" />
<Button
android:id="@+id/forgotButton"
style="@style/SudGlifButton.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginSides"
android:layout_marginEnd="?attr/sudMarginSides"
android:layout_gravity="center" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_wizard_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:icon="@drawable/ic_help_outline_32"
app:sucHeaderText="@string/forgot_password_title">
<LinearLayout
style="@style/SudContentFrame"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
style="@style/SudDescription.Glif"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/forgot_password_text" />
</LinearLayout>
</com.google.android.setupdesign.GlifLayout>

View File

@@ -4044,6 +4044,12 @@
<string name="lockpassword_choose_your_pattern_header_for_face">To use face unlock, set pattern</string> <string name="lockpassword_choose_your_pattern_header_for_face">To use face unlock, set pattern</string>
<!-- Header on first screen of choose password/PIN as backup for face unlock flow. If this string cannot be translated in under 40 characters, please translate "Set face unlock backup" [CHAR LIMIT=40] --> <!-- Header on first screen of choose password/PIN as backup for face unlock flow. If this string cannot be translated in under 40 characters, please translate "Set face unlock backup" [CHAR LIMIT=40] -->
<string name="lockpassword_choose_your_pin_header_for_face">To use face unlock, set PIN</string> <string name="lockpassword_choose_your_pin_header_for_face">To use face unlock, set PIN</string>
<!-- Text for button that the user should tap when they forgot their work profile password [CHAR LIMIT=40] -->
<string name="lockpassword_forgot_password">Forgot your password?</string>
<!-- Text for button that the user should tap when they forgot their work profile pattern [CHAR LIMIT=40] -->
<string name="lockpassword_forgot_pattern">Forgot your pattern?</string>
<!-- Text for button that the user should tap when they forgot their work profile PIN [CHAR LIMIT=40] -->
<string name="lockpassword_forgot_pin">Forgot your PIN?</string>
<!-- Message to be used to explain the user that he needs to enter his pattern to continue a <!-- Message to be used to explain the user that he needs to enter his pattern to continue a
particular operation. [CHAR LIMIT=70]--> particular operation. [CHAR LIMIT=70]-->
@@ -4215,6 +4221,11 @@
<!-- Preference title for showing all apps on device [CHAR_LIMIT=50]--> <!-- Preference title for showing all apps on device [CHAR_LIMIT=50]-->
<string name="see_all_apps_title">See all <xliff:g id="count" example="3">%1$d</xliff:g> apps</string> <string name="see_all_apps_title">See all <xliff:g id="count" example="3">%1$d</xliff:g> apps</string>
<!-- Title of the dialog that asks the user to contact the IT admin to reset password [CHAR LIMIT=40] -->
<string name="forgot_password_title">Contact your IT admin</string>
<!-- Content of the dialog that asks the user to contact the IT admin to reset password [CHAR LIMIT=100] -->
<string name="forgot_password_text">They can help you reset your PIN, pattern, or password</string>
<!-- Warning that appears below the unknown sources switch in settings --> <!-- Warning that appears below the unknown sources switch in settings -->
<string name="install_all_warning" product="tablet"> <string name="install_all_warning" product="tablet">
Your tablet and personal data are more vulnerable Your tablet and personal data are more vulnerable

View File

@@ -34,6 +34,7 @@ import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
@@ -77,6 +78,7 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
protected boolean mReturnCredentials = false; protected boolean mReturnCredentials = false;
protected Button mCancelButton; protected Button mCancelButton;
protected Button mForgotButton;
protected int mEffectiveUserId; protected int mEffectiveUserId;
protected int mUserId; protected int mUserId;
protected UserManager mUserManager; protected UserManager mUserManager;
@@ -116,8 +118,7 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
@Override @Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
mCancelButton = (Button) view.findViewById(R.id.cancelButton); mCancelButton = view.findViewById(R.id.cancelButton);
boolean showCancelButton = getActivity().getIntent().getBooleanExtra( boolean showCancelButton = getActivity().getIntent().getBooleanExtra(
SHOW_CANCEL_BUTTON, false); SHOW_CANCEL_BUTTON, false);
boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText); boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText);
@@ -126,20 +127,27 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
if (hasAlternateButton) { if (hasAlternateButton) {
mCancelButton.setText(mFrpAlternateButtonText); mCancelButton.setText(mFrpAlternateButtonText);
} }
mCancelButton.setOnClickListener(new View.OnClickListener() { mCancelButton.setOnClickListener(v -> {
@Override if (hasAlternateButton) {
public void onClick(View v) { getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
if (hasAlternateButton) {
getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
}
getActivity().finish();
} }
getActivity().finish();
}); });
int credentialOwnerUserId = Utils.getCredentialOwnerUserId( mForgotButton = view.findViewById(R.id.forgotButton);
getActivity(), if (mUserManager.isManagedProfile(mUserId)
Utils.getUserIdFromBundle( && mUserManager.isQuietModeEnabled(UserHandle.of(mUserId))
getActivity(), && mDevicePolicyManager.canProfileOwnerResetPasswordWhenLocked(mUserId)) {
getActivity().getIntent().getExtras(), isInternalActivity())); mForgotButton.setVisibility(View.VISIBLE);
mForgotButton.setOnClickListener(v -> {
final Intent intent = new Intent();
intent.setClassName(SETTINGS_PACKAGE_NAME, ForgotPasswordActivity.class.getName());
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
getActivity().startActivity(intent);
getActivity().finish();
});
} else {
mForgotButton.setVisibility(View.GONE);
}
} }
// User could be locked while Effective user is unlocked even though the effective owns the // User could be locked while Effective user is unlocked even though the effective owns the

View File

@@ -16,6 +16,7 @@
package com.android.settings.password; package com.android.settings.password;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
@@ -203,6 +204,16 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
return view; return view;
} }
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mForgotButton != null) {
mForgotButton.setText(mIsAlpha
? R.string.lockpassword_forgot_password
: R.string.lockpassword_forgot_pin);
}
}
private int getDefaultHeader() { private int getDefaultHeader() {
if (mFrp) { if (mFrp) {
return mIsAlpha ? R.string.lockpassword_confirm_your_password_header_frp return mIsAlpha ? R.string.lockpassword_confirm_your_password_header_frp
@@ -256,6 +267,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mHeaderTextView.setAlpha(0f); mHeaderTextView.setAlpha(0f);
mDetailsTextView.setAlpha(0f); mDetailsTextView.setAlpha(0f);
mCancelButton.setAlpha(0f); mCancelButton.setAlpha(0f);
mForgotButton.setAlpha(0f);
mPasswordEntry.setAlpha(0f); mPasswordEntry.setAlpha(0f);
mErrorTextView.setAlpha(0f); mErrorTextView.setAlpha(0f);
} }
@@ -267,6 +279,9 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
if (mCancelButton.getVisibility() == View.VISIBLE) { if (mCancelButton.getVisibility() == View.VISIBLE) {
result.add(mCancelButton); result.add(mCancelButton);
} }
if (mForgotButton.getVisibility() == View.VISIBLE) {
result.add(mForgotButton);
}
result.add(mPasswordEntry); result.add(mPasswordEntry);
result.add(mErrorTextView); result.add(mErrorTextView);
return result.toArray(new View[] {}); return result.toArray(new View[] {});

View File

@@ -16,6 +16,7 @@
package com.android.settings.password; package com.android.settings.password;
import android.annotation.Nullable;
import android.app.Activity; import android.app.Activity;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Intent; import android.content.Intent;
@@ -181,9 +182,18 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker, getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
FRAGMENT_TAG_CHECK_LOCK_RESULT).commit(); FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
} }
return view; return view;
} }
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mForgotButton != null) {
mForgotButton.setText(R.string.lockpassword_forgot_pattern);
}
}
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
// deliberately not calling super since we are managing this in full // deliberately not calling super since we are managing this in full
@@ -230,6 +240,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
super.prepareEnterAnimation(); super.prepareEnterAnimation();
mHeaderTextView.setAlpha(0f); mHeaderTextView.setAlpha(0f);
mCancelButton.setAlpha(0f); mCancelButton.setAlpha(0f);
mForgotButton.setAlpha(0f);
mLockPatternView.setAlpha(0f); mLockPatternView.setAlpha(0f);
mDetailsTextView.setAlpha(0f); mDetailsTextView.setAlpha(0f);
} }
@@ -252,10 +263,13 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
private Object[][] getActiveViews() { private Object[][] getActiveViews() {
ArrayList<ArrayList<Object>> result = new ArrayList<>(); ArrayList<ArrayList<Object>> result = new ArrayList<>();
result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView))); result.add(new ArrayList<>(Collections.singletonList(mHeaderTextView)));
result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView))); result.add(new ArrayList<>(Collections.singletonList(mDetailsTextView)));
if (mCancelButton.getVisibility() == View.VISIBLE) { if (mCancelButton.getVisibility() == View.VISIBLE) {
result.add(new ArrayList<Object>(Collections.singletonList(mCancelButton))); result.add(new ArrayList<>(Collections.singletonList(mCancelButton)));
}
if (mForgotButton.getVisibility() == View.VISIBLE) {
result.add(new ArrayList<>(Collections.singletonList(mForgotButton)));
} }
LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates(); LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates();
for (int i = 0; i < cellStates.length; i++) { for (int i = 0; i < cellStates.length; i++) {

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.password;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import com.android.settings.R;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
/**
* An activity that asks the user to contact their admin to get assistance with forgotten password.
*/
public class ForgotPasswordActivity extends Activity {
public static final String TAG = ForgotPasswordActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int userId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, -1);
if (userId < 0) {
Log.e(TAG, "No valid userId supplied, exiting");
finish();
return;
}
setContentView(R.layout.forgot_password_activity);
final GlifLayout layout = findViewById(R.id.setup_wizard_layout);
layout.getMixin(FooterBarMixin.class).setPrimaryButton(
new FooterButton.Builder(this)
.setText(android.R.string.ok)
.setListener(v -> finish())
.setButtonType(FooterButton.ButtonType.DONE)
.setTheme(R.style.SudGlifButton_Primary)
.build()
);
UserManager.get(this).requestQuietModeEnabled(
false, UserHandle.of(userId), UserManager.QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL);
}
}