diff options
8 files changed, 222 insertions, 4 deletions
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index faaa2e8cc0f5..d4807482f8a5 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5392,11 +5392,11 @@ <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string> <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] --> <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string> - <!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] --> + <!-- Message shown when an app being streamed to another device requests authentication such as via the biometrics API, and the user needs to complete the on their device. [CHAR LIMIT=NONE] --> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv">This app is requesting additional security. Try on your Android TV device instead.</string> - <!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] --> + <!-- Message shown when an app being streamed to another device requests authentication such as via the biometrics API, and the user needs to complete the on their device. [CHAR LIMIT=NONE] --> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet">This app is requesting additional security. Try on your tablet instead.</string> - <!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] --> + <!-- Message shown when an app being streamed to another device requests authentication such as via the biometrics API, and the user needs to complete the on their device. [CHAR LIMIT=NONE] --> <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default">This app is requesting additional security. Try on your phone instead.</string> <!-- Message shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] --> <string name="app_streaming_blocked_message_for_settings_dialog" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your Android TV device instead.</string> 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 ad4c0bf26d62..e9b9980b93eb 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()); } @@ -623,6 +624,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 f8cb9e98c714..4538cad513d6 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 6c26e2b0ce99..ece35c522ec7 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 a33533841e89..4d12574fb6b0 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -70,6 +70,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. * * @param deviceId which device we're asking about 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 000000000000..a8853071abe8 --- /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 85d159c25be2..439a6efa6f1f 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<List<FingerprintSensorPropertiesInternal>> 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); @@ -272,6 +278,47 @@ public class AuthServiceTest { } @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<Integer> 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); when(promptInfo.containsPrivateApiConfigurations()).thenReturn(false); 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 1089c07e6787..9ae56b1ed2f8 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<FingerprintAuthenticateOptions> 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<Integer> uidCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mVdmInternal).onAuthenticationPrompt(uidCaptor.capture()); + assertEquals((int) (uidCaptor.getValue()), Binder.getCallingUid()); + } + private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId( FingerprintProvider provider, long operationId) { |