"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
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4996aeb..e86f2b4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1617,6 +1617,10 @@
             android:windowSoftInputMode="stateHidden|adjustResize"
             android:theme="@style/GlifTheme.Light"/>
 
+        <activity android:name=".password.ForgotPasswordActivity"
+            android:theme="@style/GlifV3Theme.Light"
+            android:exported="false"/>
+
         <activity android:name=".biometrics.face.FaceEnrollIntroduction"
             android:exported="false"
             android:screenOrientation="portrait"/>
diff --git a/res/drawable/ic_help_outline_32.xml b/res/drawable/ic_help_outline_32.xml
new file mode 100644
index 0000000..ffe673e
--- /dev/null
+++ b/res/drawable/ic_help_outline_32.xml
@@ -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>
diff --git a/res/layout-land/confirm_lock_password.xml b/res/layout-land/confirm_lock_password.xml
index 546ef67..3d2589c 100644
--- a/res/layout-land/confirm_lock_password.xml
+++ b/res/layout-land/confirm_lock_password.xml
@@ -54,6 +54,15 @@
                 android:layout_height="wrap_content"
                 android:layout_marginStart="?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>
 
         <Space
diff --git a/res/layout-land/confirm_lock_pattern.xml b/res/layout-land/confirm_lock_pattern.xml
index 9e6133b..55e1ef2 100644
--- a/res/layout-land/confirm_lock_pattern.xml
+++ b/res/layout-land/confirm_lock_pattern.xml
@@ -70,6 +70,15 @@
                     android:layout_marginEnd="?attr/sudMarginSides"
                     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
                     android:layout_width="match_parent"
                     android:layout_height="0dp"
diff --git a/res/layout/confirm_lock_password_base.xml b/res/layout/confirm_lock_password_base.xml
index bc0c81e..9dff190 100644
--- a/res/layout/confirm_lock_password_base.xml
+++ b/res/layout/confirm_lock_password_base.xml
@@ -53,6 +53,15 @@
                 android:layout_height="wrap_content"
                 android:layout_marginStart="?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>
 
         <Space
diff --git a/res/layout/confirm_lock_pattern_base.xml b/res/layout/confirm_lock_pattern_base.xml
index 3041852..7bb4d01 100644
--- a/res/layout/confirm_lock_pattern_base.xml
+++ b/res/layout/confirm_lock_pattern_base.xml
@@ -53,7 +53,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginStart="?attr/sudMarginSides"
-                android:layout_marginEnd="?attr/sudMarginSides" />x
+                android:layout_marginEnd="?attr/sudMarginSides" />
 
             <Button
                 android:id="@+id/cancelButton"
@@ -65,6 +65,15 @@
                 android:layout_marginBottom="80dp"
                 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
diff --git a/res/layout/forgot_password_activity.xml b/res/layout/forgot_password_activity.xml
new file mode 100644
index 0000000..ed1e2d2
--- /dev/null
+++ b/res/layout/forgot_password_activity.xml
@@ -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>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ac77cc5..07ed67a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4044,6 +4044,12 @@
     <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] -->
     <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
          particular operation. [CHAR LIMIT=70]-->
@@ -4215,6 +4221,11 @@
     <!-- 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>
 
+    <!-- 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 -->
     <string name="install_all_warning" product="tablet">
         Your tablet and personal data are more vulnerable
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
index a1eb106..4422204 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
@@ -34,6 +34,7 @@
 import android.hardware.biometrics.BiometricManager;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.view.View;
@@ -77,6 +78,7 @@
 
     protected boolean mReturnCredentials = false;
     protected Button mCancelButton;
+    protected Button mForgotButton;
     protected int mEffectiveUserId;
     protected int mUserId;
     protected UserManager mUserManager;
@@ -116,8 +118,7 @@
     @Override
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        mCancelButton = (Button) view.findViewById(R.id.cancelButton);
-
+        mCancelButton = view.findViewById(R.id.cancelButton);
         boolean showCancelButton = getActivity().getIntent().getBooleanExtra(
                 SHOW_CANCEL_BUTTON, false);
         boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText);
@@ -126,20 +127,27 @@
         if (hasAlternateButton) {
             mCancelButton.setText(mFrpAlternateButtonText);
         }
-        mCancelButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                if (hasAlternateButton) {
-                    getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
-                }
-                getActivity().finish();
+        mCancelButton.setOnClickListener(v -> {
+            if (hasAlternateButton) {
+                getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
             }
+            getActivity().finish();
         });
-        int credentialOwnerUserId = Utils.getCredentialOwnerUserId(
-                getActivity(),
-                Utils.getUserIdFromBundle(
-                        getActivity(),
-                        getActivity().getIntent().getExtras(), isInternalActivity()));
+        mForgotButton = view.findViewById(R.id.forgotButton);
+        if (mUserManager.isManagedProfile(mUserId)
+                && mUserManager.isQuietModeEnabled(UserHandle.of(mUserId))
+                && mDevicePolicyManager.canProfileOwnerResetPasswordWhenLocked(mUserId)) {
+            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
diff --git a/src/com/android/settings/password/ConfirmLockPassword.java b/src/com/android/settings/password/ConfirmLockPassword.java
index a6a1b37..d77fd3a 100644
--- a/src/com/android/settings/password/ConfirmLockPassword.java
+++ b/src/com/android/settings/password/ConfirmLockPassword.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.password;
 
+import android.annotation.Nullable;
 import android.app.admin.DevicePolicyManager;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
@@ -203,6 +204,16 @@
             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() {
             if (mFrp) {
                 return mIsAlpha ? R.string.lockpassword_confirm_your_password_header_frp
@@ -256,6 +267,7 @@
             mHeaderTextView.setAlpha(0f);
             mDetailsTextView.setAlpha(0f);
             mCancelButton.setAlpha(0f);
+            mForgotButton.setAlpha(0f);
             mPasswordEntry.setAlpha(0f);
             mErrorTextView.setAlpha(0f);
         }
@@ -267,6 +279,9 @@
             if (mCancelButton.getVisibility() == View.VISIBLE) {
                 result.add(mCancelButton);
             }
+            if (mForgotButton.getVisibility() == View.VISIBLE) {
+                result.add(mForgotButton);
+            }
             result.add(mPasswordEntry);
             result.add(mErrorTextView);
             return result.toArray(new View[] {});
diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java
index d52992f..fd164b8 100644
--- a/src/com/android/settings/password/ConfirmLockPattern.java
+++ b/src/com/android/settings/password/ConfirmLockPattern.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.password;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.settings.SettingsEnums;
 import android.content.Intent;
@@ -181,10 +182,19 @@
                 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
                         FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
             }
+
             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
         public void onSaveInstanceState(Bundle outState) {
             // deliberately not calling super since we are managing this in full
         }
@@ -230,6 +240,7 @@
             super.prepareEnterAnimation();
             mHeaderTextView.setAlpha(0f);
             mCancelButton.setAlpha(0f);
+            mForgotButton.setAlpha(0f);
             mLockPatternView.setAlpha(0f);
             mDetailsTextView.setAlpha(0f);
         }
@@ -252,10 +263,13 @@
 
         private Object[][] getActiveViews() {
             ArrayList<ArrayList<Object>> result = new ArrayList<>();
-            result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView)));
-            result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView)));
+            result.add(new ArrayList<>(Collections.singletonList(mHeaderTextView)));
+            result.add(new ArrayList<>(Collections.singletonList(mDetailsTextView)));
             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();
             for (int i = 0; i < cellStates.length; i++) {
diff --git a/src/com/android/settings/password/ForgotPasswordActivity.java b/src/com/android/settings/password/ForgotPasswordActivity.java
new file mode 100644
index 0000000..1f1df18
--- /dev/null
+++ b/src/com/android/settings/password/ForgotPasswordActivity.java
@@ -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);
+    }
+}