From 734a40cf8f67542e3ee9fdcab942416caf5d761e Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Wed, 3 May 2023 15:23:58 -0700 Subject: [PATCH] Warn the user when a streamed app requests authentication When a user is streaming an app running on a VirtualDisplay owned by a VirtualDevice, and that app requests authentication via the BiometricPrompt api, the streamed view of the app appears frozen and the user may not understand that they need to go to their phone to complete the authentication. This CL adds a toast we show in this situation to help the user know what to do. Bug: 246425400 Test: atest AuthServiceTest VirtualDeviceManagerServiceMockingTest Change-Id: I5c88832c948545e9aab1eb23cca97a3165008dae --- core/res/res/values/strings.xml | 6 +- .../virtual/VirtualDeviceManagerService.java | 15 ++- .../server/biometrics/AuthService.java | 6 ++ .../fingerprint/FingerprintService.java | 11 +++ .../virtual/VirtualDeviceManagerInternal.java | 5 + ...irtualDeviceManagerServiceMockingTest.java | 96 +++++++++++++++++++ .../server/biometrics/AuthServiceTest.java | 47 +++++++++ .../fingerprint/FingerprintServiceTest.java | 40 ++++++++ 8 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 services/tests/mockingservicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceMockingTest.java diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 027d4f8ab2b53..5fd6e5f35b98c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5403,11 +5403,11 @@ This can’t be accessed on your %1$s at this time. Try on your tablet instead. This can’t be accessed on your %1$s at this time. Try on your phone instead. - + This app is requesting additional security. Try on your Android TV device instead. - + This app is requesting additional security. Try on your tablet instead. - + This app is requesting additional security. Try on your phone instead. This can’t be accessed on your %1$s. Try on your Android TV device instead. diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index ad4c0bf26d62b..e9b9980b93eb6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -55,6 +55,7 @@ import android.util.SparseArray; import android.view.Display; import android.widget.Toast; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; @@ -156,7 +157,7 @@ public class VirtualDeviceManagerService extends SystemService { VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i); virtualDevice.showToastWhereUidIsRunning(appUid, getContext().getString( - com.android.internal.R.string.vdm_camera_access_denied, + R.string.vdm_camera_access_denied, virtualDevice.getDisplayName()), Toast.LENGTH_LONG, Looper.myLooper()); } @@ -622,6 +623,18 @@ public class VirtualDeviceManagerService extends SystemService { } } + @Override + public void onAuthenticationPrompt(int uid) { + synchronized (mVirtualDeviceManagerLock) { + for (int i = 0; i < mVirtualDevices.size(); i++) { + VirtualDeviceImpl device = mVirtualDevices.valueAt(i); + device.showToastWhereUidIsRunning(uid, + R.string.app_streaming_blocked_message_for_fingerprint_dialog, + Toast.LENGTH_LONG, Looper.getMainLooper()); + } + } + } + @Override public int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice) { return ((VirtualDeviceImpl) virtualDevice).getBaseVirtualDisplayFlags(); diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index acfc2a72d503f..9c590efab7de4 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -70,6 +70,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.server.SystemService; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import java.util.ArrayList; import java.util.Arrays; @@ -262,6 +263,11 @@ public class AuthService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { + VirtualDeviceManagerInternal vdm = getLocalService( + VirtualDeviceManagerInternal.class); + if (vdm != null) { + vdm.onAuthenticationPrompt(callingUid); + } return mBiometricService.authenticate( token, sessionId, userId, receiver, opPackageName, promptInfo); } finally { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 6c26e2b0ce998..ece35c522ec79 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -91,6 +91,7 @@ import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider; import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21; import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.google.android.collect.Lists; @@ -329,6 +330,16 @@ public class FingerprintService extends SystemService { return -1; } } + final long identity2 = Binder.clearCallingIdentity(); + try { + VirtualDeviceManagerInternal vdm = getLocalService( + VirtualDeviceManagerInternal.class); + if (vdm != null) { + vdm.onAuthenticationPrompt(callingUid); + } + } finally { + Binder.restoreCallingIdentity(identity2); + } return provider.second.scheduleAuthenticate(token, operationId, 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options, restricted, statsClient, isKeyguard); diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index a33533841e89e..4d12574fb6b07 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -69,6 +69,11 @@ public abstract class VirtualDeviceManagerInternal { */ public abstract void onAppsOnVirtualDeviceChanged(); + /** + * Notifies that an authentication prompt is about to be shown for an app with the given uid. + */ + public abstract void onAuthenticationPrompt(int uid); + /** * Gets the owner uid for a deviceId. * diff --git a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceMockingTest.java new file mode 100644 index 0000000000000..a8853071abe82 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceMockingTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 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.server.companion.virtual; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Looper; +import android.platform.test.annotations.Presubmit; +import android.testing.TestableContext; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class VirtualDeviceManagerServiceMockingTest { + private static final int UID_1 = 0; + private static final int DEVICE_ID_1 = 42; + private static final int DEVICE_ID_2 = 43; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getContext()); + + private VirtualDeviceManagerService mVdms; + private VirtualDeviceManagerInternal mLocalService; + + @Before + public void setUp() { + mVdms = new VirtualDeviceManagerService(mContext); + mLocalService = mVdms.getLocalServiceInstance(); + } + + @Test + public void onAuthenticationPrompt_noDevices_noCrash() { + // This should not crash + mLocalService.onAuthenticationPrompt(UID_1); + } + + @Test + public void onAuthenticationPrompt_oneDevice_showToastWhereUidIsRunningIsCalled() { + VirtualDeviceImpl device = mock(VirtualDeviceImpl.class); + mVdms.addVirtualDevice(device); + + mLocalService.onAuthenticationPrompt(UID_1); + + verify(device).showToastWhereUidIsRunning(eq(UID_1), + eq(R.string.app_streaming_blocked_message_for_fingerprint_dialog), anyInt(), + any(Looper.class)); + } + + @Test + public void onAuthenticationPrompt_twoDevices_showToastWhereUidIsRunningIsCalledOnBoth() { + VirtualDeviceImpl device1 = mock(VirtualDeviceImpl.class); + VirtualDeviceImpl device2 = mock(VirtualDeviceImpl.class); + when(device1.getDeviceId()).thenReturn(DEVICE_ID_1); + when(device2.getDeviceId()).thenReturn(DEVICE_ID_2); + mVdms.addVirtualDevice(device1); + mVdms.addVirtualDevice(device2); + + mLocalService.onAuthenticationPrompt(UID_1); + + verify(device1).showToastWhereUidIsRunning(eq(UID_1), + eq(R.string.app_streaming_blocked_message_for_fingerprint_dialog), anyInt(), + any(Looper.class)); + verify(device2).showToastWhereUidIsRunning(eq(UID_1), + eq(R.string.app_streaming_blocked_message_for_fingerprint_dialog), anyInt(), + any(Looper.class)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index 4b86dd048cd1f..c78aa47d1ab1a 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -58,6 +58,8 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.server.LocalServices; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import org.junit.Before; import org.junit.Rule; @@ -102,6 +104,8 @@ public class AuthServiceTest { IFaceService mFaceService; @Mock AppOpsManager mAppOpsManager; + @Mock + private VirtualDeviceManagerInternal mVdmInternal; @Captor private ArgumentCaptor> mFingerprintPropsCaptor; @Captor @@ -115,6 +119,8 @@ public class AuthServiceTest { "1:4:15", // ID1:Iris:Strong "2:8:15", // ID2:Face:Strong }; + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); + LocalServices.addService(VirtualDeviceManagerInternal.class, mVdmInternal); when(mResources.getIntArray(eq(R.array.config_udfps_sensor_props))).thenReturn(new int[0]); when(mResources.getBoolean(eq(R.bool.config_is_powerbutton_fps))).thenReturn(false); @@ -271,6 +277,47 @@ public class AuthServiceTest { verify(mReceiver).onError(eq(TYPE_NONE), eq(BIOMETRIC_ERROR_CANCELED), anyInt()); } + @Test + public void testAuthenticate_noVdmInternalService_noCrash() throws Exception { + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + final Binder token = new Binder(); + + // This should not crash + mAuthService.mImpl.authenticate( + token, + 0, /* sessionId */ + 0, /* userId */ + mReceiver, + TEST_OP_PACKAGE_NAME, + new PromptInfo()); + waitForIdle(); + } + + @Test + public void testAuthenticate_callsVirtualDeviceManagerOnAuthenticationPrompt() + throws Exception { + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + final Binder token = new Binder(); + + mAuthService.mImpl.authenticate( + token, + 0, /* sessionId */ + 0, /* userId */ + mReceiver, + TEST_OP_PACKAGE_NAME, + new PromptInfo()); + waitForIdle(); + + ArgumentCaptor uidCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mVdmInternal).onAuthenticationPrompt(uidCaptor.capture()); + assertEquals((int) (uidCaptor.getValue()), Binder.getCallingUid()); + } + @Test public void testAuthenticate_throwsWhenUsingTestConfigurations() { final PromptInfo promptInfo = mock(PromptInfo.class); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java index 1089c07e6787d..9ae56b1ed2f8c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java @@ -27,6 +27,8 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFP import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -44,6 +46,7 @@ import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.Binder; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.provider.Settings; @@ -54,8 +57,10 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; +import com.android.server.LocalServices; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import org.junit.Before; import org.junit.Rule; @@ -103,6 +108,8 @@ public class FingerprintServiceTest { private IFingerprintServiceReceiver mServiceReceiver; @Mock private IBinder mToken; + @Mock + private VirtualDeviceManagerInternal mVdmInternal; @Captor private ArgumentCaptor mAuthenticateOptionsCaptor; @@ -125,6 +132,9 @@ public class FingerprintServiceTest { @Before public void setup() throws Exception { + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); + LocalServices.addService(VirtualDeviceManagerInternal.class, mVdmInternal); + when(mFingerprintDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault)); when(mFingerprintVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual)); when(mFingerprintDefault.containsSensor(anyInt())) @@ -225,6 +235,36 @@ public class FingerprintServiceTest { verifyNoAuthenticate(mFingerprintVirtual); } + @Test + public void testAuthenticate_noVdmInternalService_noCrash() throws Exception { + initServiceWithAndWait(NAME_DEFAULT, NAME_VIRTUAL); + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); + + final long operationId = 2; + + // This should not crash + mService.mServiceWrapper.authenticate(mToken, operationId, mServiceReceiver, + new FingerprintAuthenticateOptions.Builder() + .setSensorId(SENSOR_ID_ANY) + .build()); + } + + @Test + public void testAuthenticate_callsVirtualDeviceManagerOnAuthenticationPrompt() + throws Exception { + initServiceWithAndWait(NAME_DEFAULT, NAME_VIRTUAL); + + final long operationId = 2; + mService.mServiceWrapper.authenticate(mToken, operationId, mServiceReceiver, + new FingerprintAuthenticateOptions.Builder() + .setSensorId(SENSOR_ID_ANY) + .build()); + + ArgumentCaptor uidCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mVdmInternal).onAuthenticationPrompt(uidCaptor.capture()); + assertEquals((int) (uidCaptor.getValue()), Binder.getCallingUid()); + } + private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId( FingerprintProvider provider, long operationId) {