Prompt for screen lock setup during private space unlock

This change introduces a dialog that enables the user to set up the
screen lock if it's not already enabled when they attempt to unlock a
private space.

Bug: 316129700
Test: atest PrivateSpaceAuthenticationActivityTest
Flag: android.multiuser.show_set_screen_lock_dialog

Change-Id: Iac3dd5762c0d082e3604df053873bb9195880991
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 5684929..1513e9a 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -144,6 +144,13 @@
 }
 
 flag {
+    name: "show_set_screen_lock_dialog"
+    namespace: "profile_experiences"
+    description: "Display the dialog to set up screen lock when private space unlock operation is requested"
+    bug: "316129700"
+}
+
+flag {
     name: "reorder_wallpaper_during_user_switch"
     namespace: "multiuser"
     description: "Reorder loading home and lock screen wallpapers during a user switch."
@@ -162,4 +169,4 @@
     namespace: "profile_experiences"
     description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically"
     bug: "287975131"
-}
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
new file mode 100644
index 0000000..93fe37c
--- /dev/null
+++ b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 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.internal.app;
+
+import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.hardware.biometrics.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
+import static android.provider.Settings.ACTION_BIOMETRIC_ENROLL;
+import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A dialog shown to the user that prompts them to set the screen lock for the current foreground
+ * user. Should be called from the context of foreground user.
+ */
+public class SetScreenLockDialogActivity extends AlertActivity
+        implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+    private static final String TAG = "SetScreenLockDialog";
+    public static final String EXTRA_LAUNCH_REASON = "launch_reason";
+    /**
+     * User id associated with the workflow that wants to launch the prompt to set up the
+     * screen lock
+     */
+    public static final String EXTRA_ORIGIN_USER_ID = "origin_user_id";
+    private static final String PACKAGE_NAME = "android";
+    @IntDef(prefix = "LAUNCH_REASON_", value = {
+            LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS,
+            LAUNCH_REASON_DISABLE_QUIET_MODE,
+            LAUNCH_REASON_UNKNOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LaunchReason {
+    }
+    public static final int LAUNCH_REASON_UNKNOWN = -1;
+    public static final int LAUNCH_REASON_DISABLE_QUIET_MODE = 1;
+    public static final int LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS = 2;
+    private @LaunchReason int mReason;
+    private int mOriginUserId;
+
+    @Override
+    @RequiresPermission(HIDE_OVERLAY_WINDOWS)
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (!(android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.showSetScreenLockDialog())) {
+            finish();
+            return;
+        }
+        Intent intent = getIntent();
+        mReason = intent.getIntExtra(EXTRA_LAUNCH_REASON, LAUNCH_REASON_UNKNOWN);
+        mOriginUserId = intent.getIntExtra(EXTRA_ORIGIN_USER_ID, UserHandle.USER_NULL);
+
+        if (mReason == LAUNCH_REASON_UNKNOWN) {
+            Log.e(TAG, "Invalid launch reason: " + mReason);
+            finish();
+            return;
+        }
+
+        final KeyguardManager km = getSystemService(KeyguardManager.class);
+        if (km == null) {
+            Log.e(TAG, "Error fetching keyguard manager");
+            return;
+        }
+        if (km.isDeviceSecure()) {
+            Log.w(TAG, "Closing the activity since screen lock is already set");
+            return;
+        }
+
+        Log.d(TAG, "Launching screen lock setup dialog due to " + mReason);
+        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle(R.string.set_up_screen_lock_title)
+                .setOnDismissListener(this)
+                .setPositiveButton(R.string.set_up_screen_lock_action_label, this)
+                .setNegativeButton(R.string.cancel, this);
+        setLaunchUserSpecificMessage(builder);
+        final AlertDialog dialog = builder.create();
+        dialog.create();
+        getWindow().setHideOverlayWindows(true);
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+        dialog.show();
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        finish();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            Intent setNewLockIntent = new Intent(ACTION_BIOMETRIC_ENROLL);
+            setNewLockIntent.putExtra(EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, DEVICE_CREDENTIAL);
+            startActivity(setNewLockIntent);
+        } else {
+            finish();
+        }
+    }
+
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
+    private void setLaunchUserSpecificMessage(AlertDialog.Builder builder) {
+        if (mReason == LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS) {
+            // Always set private space message if launch reason is specific to private space
+            builder.setMessage(R.string.private_space_set_up_screen_lock_message);
+            return;
+        }
+        final UserManager userManager = getApplicationContext().getSystemService(UserManager.class);
+        if (userManager != null) {
+            UserInfo userInfo = userManager.getUserInfo(mOriginUserId);
+            if (userInfo != null && userInfo.isPrivateProfile()) {
+                builder.setMessage(R.string.private_space_set_up_screen_lock_message);
+            }
+        }
+    }
+
+    /** Returns a basic intent to display the screen lock dialog */
+    public static Intent createBaseIntent(@LaunchReason int launchReason) {
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName(PACKAGE_NAME,
+                SetScreenLockDialogActivity.class.getName()));
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        intent.putExtra(EXTRA_LAUNCH_REASON, launchReason);
+        return intent;
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0b3a065..952c8f1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8370,6 +8370,12 @@
                 android:process=":ui">
         </activity>
 
+        <activity android:name="com.android.internal.app.SetScreenLockDialogActivity"
+                  android:theme="@style/Theme.Dialog.Confirmation"
+                  android:excludeFromRecents="true"
+                  android:process=":ui">
+        </activity>
+
         <activity android:name="com.android.internal.app.BlockedAppActivity"
                 android:theme="@style/Theme.Dialog.Confirmation"
                 android:excludeFromRecents="true"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e999695..59066eb 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5448,6 +5448,13 @@
     <!-- Title for button to launch the personal safety app to make an emergency call    -->
     <string name="work_mode_emergency_call_button">Emergency</string>
 
+    <!-- Title of the alert dialog prompting the user to set up a screen lock [CHAR LIMIT=30] -->
+    <string name="set_up_screen_lock_title">Set a screen lock</string>
+    <!-- Action label for the dialog prompting the user to set up a screen lock [CHAR LIMIT=30] -->
+    <string name="set_up_screen_lock_action_label">Set screen lock</string>
+    <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=30] -->
+    <string name="private_space_set_up_screen_lock_message">To use your private space, set a screen lock on this device</string>
+
     <!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
     <string name="app_blocked_title">App is not available</string>
     <!-- Default message shown in the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9d7acff..fe4434c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3247,6 +3247,12 @@
   <java-symbol type="string" name="work_mode_off_title" />
   <java-symbol type="string" name="work_mode_turn_on" />
 
+  <!-- Alert dialog prompting the user to set up a screen lock -->
+  <java-symbol type="string" name="set_up_screen_lock_title" />
+  <java-symbol type="string" name="set_up_screen_lock_action_label" />
+  <!-- Message for the alert dialog prompting the user to set up a screen lock to access private space -->
+  <java-symbol type="string" name="private_space_set_up_screen_lock_message" />
+
   <java-symbol type="string" name="deprecated_target_sdk_message" />
   <java-symbol type="string" name="deprecated_target_sdk_app_store" />
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f222fe9..a203975 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.content.Intent.ACTION_SCREEN_OFF;
 import static android.content.Intent.ACTION_SCREEN_ON;
+import static android.content.Intent.EXTRA_USER_ID;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
@@ -25,6 +26,8 @@
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
 
+import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
+import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE;
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
 import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
@@ -137,6 +140,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.SetScreenLockDialogActivity;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.RoSystemProperties;
@@ -1676,6 +1680,10 @@
                 synchronized (mUsersLock) {
                     userInfo = getUserInfo(userId);
                 }
+                if (userInfo == null) {
+                    throw new IllegalArgumentException("Invalid user. Can't find user details "
+                            + "for userId " + userId);
+                }
                 if (!userInfo.isManagedProfile()) {
                     throw new IllegalArgumentException("Invalid flags: " + flags
                             + ". Can't skip credential check for the user");
@@ -1692,6 +1700,19 @@
                     if (onlyIfCredentialNotRequired) {
                         return false;
                     }
+
+                    if (android.multiuser.Flags.showSetScreenLockDialog()) {
+                        // Show the prompt to set a new screen lock if the device does not have one
+                        final KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
+                        if (km != null && !km.isDeviceSecure()) {
+                            Intent setScreenLockPromptIntent =
+                                    SetScreenLockDialogActivity
+                                            .createBaseIntent(LAUNCH_REASON_DISABLE_QUIET_MODE);
+                            setScreenLockPromptIntent.putExtra(EXTRA_ORIGIN_USER_ID, userId);
+                            mContext.startActivity(setScreenLockPromptIntent);
+                            return false;
+                        }
+                    }
                     showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
                     return false;
                 }
@@ -1915,7 +1936,7 @@
         if (target != null) {
             callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
         }
-        callBackIntent.putExtra(Intent.EXTRA_USER_ID, userId);
+        callBackIntent.putExtra(EXTRA_USER_ID, userId);
         callBackIntent.setPackage(mContext.getPackageName());
         callBackIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackage);
         callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);