Add a dialog prompting the user to unlock keyguard

Shown when an activity redirection to the default display happens
but the keyguard is locked. Dismissed automatically when the keyguard
is unlocked.

Bug: 333443509
Test: manual
Flag: android.companion.virtualdevice.flags.activity_control_api
Change-Id: I4dfa150ac681ca5529fcb812887ce8e26bdb3570
diff --git a/samples/VirtualDeviceManager/host/AndroidManifest.xml b/samples/VirtualDeviceManager/host/AndroidManifest.xml
index 2c8c8ca..bd19fd7 100644
--- a/samples/VirtualDeviceManager/host/AndroidManifest.xml
+++ b/samples/VirtualDeviceManager/host/AndroidManifest.xml
@@ -36,6 +36,9 @@
     <uses-permission
         android:name="android.permission.ADD_TRUSTED_DISPLAY"
         tools:ignore="ProtectedPermissions" />
+    <uses-permission
+        android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
+        tools:ignore="ProtectedPermissions" />
 
     <queries>
         <intent>
@@ -73,7 +76,13 @@
             android:exported="true"
             android:launchMode="singleTop"
             android:theme="@style/AppTheme.FullScreen" />
-
+        <activity
+            android:name=".UnlockKeyguardDialog"
+            android:exported="false"
+            android:excludeFromRecents="true"
+            android:launchMode="singleInstance"
+            android:label="@string/unlock_dialog_title"
+            android:theme="@style/Theme.AppCompat.Dialog.Alert" />
         <service
             android:name=".VdmService"
             android:exported="false"
diff --git a/samples/VirtualDeviceManager/host/res/layout/unlock_keyguard_dialog.xml b/samples/VirtualDeviceManager/host/res/layout/unlock_keyguard_dialog.xml
new file mode 100644
index 0000000..528e111
--- /dev/null
+++ b/samples/VirtualDeviceManager/host/res/layout/unlock_keyguard_dialog.xml
@@ -0,0 +1,21 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/message_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="20dp"
+        android:gravity="center" />
+
+    <Button
+        android:text="Cancel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="end"
+        android:onClick="onCanceled" />
+
+</LinearLayout>
diff --git a/samples/VirtualDeviceManager/host/res/values/strings.xml b/samples/VirtualDeviceManager/host/res/values/strings.xml
index 4b802f0..f912bdc 100644
--- a/samples/VirtualDeviceManager/host/res/values/strings.xml
+++ b/samples/VirtualDeviceManager/host/res/values/strings.xml
@@ -35,6 +35,8 @@
 
     <string name="custom_activity_launch_blocked_message" translatable="false">Can\'t show %s here, try on your %s</string>
     <string name="custom_activity_launch_fallback_message" translatable="false">Launched %s on your %s</string>
+    <string name="unlock_dialog_title" translatable="false">Device locked</string>
+    <string name="unlock_dialog_message" translatable="false">Unlock your %s to continue.</string>
 
     <string name="pref_device_profile" translatable="false">device_profile</string>
     <string name="pref_hide_from_recents" translatable="false">hide_from_recents</string>
diff --git a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/UnlockKeyguardDialog.java b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/UnlockKeyguardDialog.java
new file mode 100644
index 0000000..adc9984
--- /dev/null
+++ b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/UnlockKeyguardDialog.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 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.example.android.vdmdemo.host;
+
+import android.app.ActivityOptions;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+/** A dialog prompting the user to unlock the default display for a fallback activity launch. */
+@AndroidEntryPoint(AppCompatActivity.class)
+public class UnlockKeyguardDialog extends Hilt_UnlockKeyguardDialog
+        implements KeyguardManager.KeyguardLockedStateListener {
+    private static final String TAG = "UnlockKeyguardDialog";
+
+    private IntentSender mTarget;
+    private KeyguardManager mKeyguardManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Intent intent = getIntent();
+        mTarget = intent.getParcelableExtra(Intent.EXTRA_INTENT,
+                android.content.IntentSender.class);
+        if (mTarget == null) {
+            Log.wtf(TAG, "Invalid intent: " + intent);
+            finish();
+        }
+
+        mKeyguardManager = getSystemService(KeyguardManager.class);
+        if (mKeyguardManager == null) {
+            Log.wtf(TAG, "KeyguardManager not available");
+            finish();
+        }
+
+        setContentView(R.layout.unlock_keyguard_dialog);
+        ((TextView) requireViewById(R.id.message_view))
+                .setText(getString(R.string.unlock_dialog_message, Build.MODEL));
+
+        if (!mKeyguardManager.isKeyguardLocked()) {
+            onKeyguardLockedStateChanged(false);
+        } else {
+            mKeyguardManager.addKeyguardLockedStateListener(getMainExecutor(), this);
+        }
+
+        setFinishOnTouchOutside(true);
+    }
+
+    @Override
+    public void onNewIntent(@NonNull Intent intent) {
+        super.onNewIntent(intent);
+        setIntent(intent);
+    }
+
+    @Override
+    protected void onDestroy() {
+        mKeyguardManager.removeKeyguardLockedStateListener(this);
+        super.onDestroy();
+    }
+
+    @Override
+    public void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
+        if (isKeyguardLocked) return;
+
+        Bundle activityOptions =
+                ActivityOptions.makeBasic()
+                        .setPendingIntentBackgroundActivityStartMode(
+                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                        .setLaunchDisplayId(Display.DEFAULT_DISPLAY)
+                        .toBundle();
+        try {
+            startIntentSender(mTarget, /* fillInIntent= */ null, /* flagsMask= */ 0,
+                    /* flagsValues= */ 0, /* extraFlags= */ 0, activityOptions);
+        } catch (IntentSender.SendIntentException e) {
+            Log.e(TAG, "Error while starting intent sender", e);
+        }
+        finish();
+    }
+
+    /** Called when the dialog has been canceled by the user. */
+    public void onCanceled(View view) {
+        finish();
+    }
+
+    /**
+     * Creates an intent that launches UnlockKeyguardDialog when an activity launch is blocked.
+     */
+    public static Intent createIntent(Context context, IntentSender target) {
+        return new Intent()
+                .setClass(context, UnlockKeyguardDialog.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .putExtra(Intent.EXTRA_INTENT, target);
+    }
+}
diff --git a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/VdmService.java b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/VdmService.java
index 585b194..3550a8c 100644
--- a/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/VdmService.java
+++ b/samples/VirtualDeviceManager/host/src/com/example/android/vdmdemo/host/VdmService.java
@@ -16,6 +16,7 @@
 
 package com.example.android.vdmdemo.host;
 
+import static android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED;
@@ -47,6 +48,7 @@
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.drawable.Icon;
 import android.hardware.display.DisplayManager;
@@ -180,13 +182,27 @@
                 return;
             }
 
-            if (intentSender == null || mKeyguardManager.isKeyguardLocked()) {
-                // TODO(b/333443509): Show a dialog prompting to unlock if keyguard is locked
+            if (intentSender == null) {
                 showToast(displayId, componentName,
                         R.string.custom_activity_launch_blocked_message);
                 return;
             }
 
+            // When the keyguard is locked, show a dialog prompting the user to unlock it.
+            if (mKeyguardManager.isKeyguardLocked()) {
+                // TODO(b/333443509): remove this check once the permission is in to the VDM roles
+                if (checkCallingOrSelfPermission(SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    showToast(displayId, componentName,
+                            R.string.custom_activity_launch_blocked_message);
+                } else {
+                    startActivity(
+                            UnlockKeyguardDialog.createIntent(VdmService.this, intentSender),
+                            ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle());
+                }
+                return;
+            }
+
             // Try to launch the activity on the default display with NEW_TASK flag.
             Intent fillInIntent = new Intent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             ActivityOptions activityOptions = ActivityOptions.makeBasic()