Merge "Warn the user when a streamed app requests authentication" into udc-dev

This commit is contained in:
Antony Sargent
2023-05-11 19:52:41 +00:00
committed by Android (Google) Code Review
8 changed files with 222 additions and 4 deletions

View File

@@ -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> <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] --> <!-- 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> <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> <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> <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> <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] --> <!-- 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> <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>

View File

@@ -55,6 +55,7 @@ import android.util.SparseArray;
import android.view.Display; import android.view.Display;
import android.widget.Toast; import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils; import com.android.internal.util.DumpUtils;
@@ -156,7 +157,7 @@ public class VirtualDeviceManagerService extends SystemService {
VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i); VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
virtualDevice.showToastWhereUidIsRunning(appUid, virtualDevice.showToastWhereUidIsRunning(appUid,
getContext().getString( getContext().getString(
com.android.internal.R.string.vdm_camera_access_denied, R.string.vdm_camera_access_denied,
virtualDevice.getDisplayName()), virtualDevice.getDisplayName()),
Toast.LENGTH_LONG, Looper.myLooper()); 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 @Override
public int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice) { public int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice) {
return ((VirtualDeviceImpl) virtualDevice).getBaseVirtualDisplayFlags(); return ((VirtualDeviceImpl) virtualDevice).getBaseVirtualDisplayFlags();

View File

@@ -70,6 +70,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils; import com.android.internal.util.ArrayUtils;
import com.android.server.SystemService; import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -262,6 +263,11 @@ public class AuthService extends SystemService {
final long identity = Binder.clearCallingIdentity(); final long identity = Binder.clearCallingIdentity();
try { try {
VirtualDeviceManagerInternal vdm = getLocalService(
VirtualDeviceManagerInternal.class);
if (vdm != null) {
vdm.onAuthenticationPrompt(callingUid);
}
return mBiometricService.authenticate( return mBiometricService.authenticate(
token, sessionId, userId, receiver, opPackageName, promptInfo); token, sessionId, userId, receiver, opPackageName, promptInfo);
} finally { } finally {

View File

@@ -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.aidl.FingerprintProvider;
import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21; import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21;
import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock; import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.google.android.collect.Lists; import com.google.android.collect.Lists;
@@ -329,6 +330,16 @@ public class FingerprintService extends SystemService {
return -1; 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, return provider.second.scheduleAuthenticate(token, operationId,
0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options, 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
restricted, statsClient, isKeyguard); restricted, statsClient, isKeyguard);

View File

@@ -69,6 +69,11 @@ public abstract class VirtualDeviceManagerInternal {
*/ */
public abstract void onAppsOnVirtualDeviceChanged(); 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. * Gets the owner uid for a deviceId.
* *

View File

@@ -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));
}
}

View File

@@ -58,6 +58,8 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.internal.R; import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@@ -102,6 +104,8 @@ public class AuthServiceTest {
IFaceService mFaceService; IFaceService mFaceService;
@Mock @Mock
AppOpsManager mAppOpsManager; AppOpsManager mAppOpsManager;
@Mock
private VirtualDeviceManagerInternal mVdmInternal;
@Captor @Captor
private ArgumentCaptor<List<FingerprintSensorPropertiesInternal>> mFingerprintPropsCaptor; private ArgumentCaptor<List<FingerprintSensorPropertiesInternal>> mFingerprintPropsCaptor;
@Captor @Captor
@@ -115,6 +119,8 @@ public class AuthServiceTest {
"1:4:15", // ID1:Iris:Strong "1:4:15", // ID1:Iris:Strong
"2:8:15", // ID2:Face: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.getIntArray(eq(R.array.config_udfps_sensor_props))).thenReturn(new int[0]);
when(mResources.getBoolean(eq(R.bool.config_is_powerbutton_fps))).thenReturn(false); 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()); 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<Integer> uidCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mVdmInternal).onAuthenticationPrompt(uidCaptor.capture());
assertEquals((int) (uidCaptor.getValue()), Binder.getCallingUid());
}
@Test @Test
public void testAuthenticate_throwsWhenUsingTestConfigurations() { public void testAuthenticate_throwsWhenUsingTestConfigurations() {
final PromptInfo promptInfo = mock(PromptInfo.class); final PromptInfo promptInfo = mock(PromptInfo.class);

View File

@@ -27,6 +27,8 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFP
import static com.google.common.truth.Truth.assertThat; 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.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
@@ -44,6 +46,7 @@ import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.provider.Settings; 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.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider; import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@@ -103,6 +108,8 @@ public class FingerprintServiceTest {
private IFingerprintServiceReceiver mServiceReceiver; private IFingerprintServiceReceiver mServiceReceiver;
@Mock @Mock
private IBinder mToken; private IBinder mToken;
@Mock
private VirtualDeviceManagerInternal mVdmInternal;
@Captor @Captor
private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor; private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
@@ -125,6 +132,9 @@ public class FingerprintServiceTest {
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
LocalServices.addService(VirtualDeviceManagerInternal.class, mVdmInternal);
when(mFingerprintDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault)); when(mFingerprintDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
when(mFingerprintVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual)); when(mFingerprintVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
when(mFingerprintDefault.containsSensor(anyInt())) when(mFingerprintDefault.containsSensor(anyInt()))
@@ -225,6 +235,36 @@ public class FingerprintServiceTest {
verifyNoAuthenticate(mFingerprintVirtual); 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( private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
FingerprintProvider provider, long operationId) { FingerprintProvider provider, long operationId) {