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()