Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ce7efc29 authored by Md Shahadat Hossain Shahin's avatar Md Shahadat Hossain Shahin
Browse files

Allow overriding default visibility for sensitive inputs

Allows overriding the default values of the settings that control the
visibility of sensitive inputs such as passwords, PINs and patterns via
config.xml.

This implementation is a temporary measure pending a permanent solution,
which is being tracked under b/417951523. The previously considered
approach, which would have determined the default based on the
availability of physical input device, was set aside to prioritize the
development of the permanent solution.

Bug: 339270220
Test: Manual
Test: atest LockPatternUtilsTest
Flag: com.android.internal.widget.flags.use_default_visibility_for_sensitive_inputs
Change-Id: I2a2a4261fe161a14c5093332c24dd6df5ecd9c29
parent ecc4d1a9
Loading
Loading
Loading
Loading
+12 −35
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;

import static com.android.internal.widget.flags.Flags.hideLastCharWithPhysicalInput;
import static com.android.internal.widget.flags.Flags.useDefaultVisibilityForSensitiveInputs;

import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -42,7 +42,6 @@ import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.input.InputManagerGlobal;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -60,7 +59,6 @@ import android.util.Log;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.view.InputDevice;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -1065,18 +1063,17 @@ public class LockPatternUtils {
        return type == CREDENTIAL_TYPE_PATTERN;
    }

    private boolean hasActivePointerDeviceAttached() {
        return !getEnabledNonTouchInputDevices(InputDevice.SOURCE_CLASS_POINTER).isEmpty();
    }

    /**
     * @return Whether the visible pattern is enabled.
     */
    @UnsupportedAppUsage
    public boolean isVisiblePatternEnabled(int userId) {
        boolean defaultValue = true;
        if (hideLastCharWithPhysicalInput()) {
            defaultValue = !hasActivePointerDeviceAttached();
        if (useDefaultVisibilityForSensitiveInputs()) {
            defaultValue =
                    mContext.getResources()
                            .getBoolean(
                                    com.android.internal.R.bool.config_lockPatternVisibleDefault);
        }
        return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, defaultValue, userId);
    }
@@ -1092,37 +1089,17 @@ public class LockPatternUtils {
        return getString(Settings.Secure.LOCK_PATTERN_VISIBLE, userId) != null;
    }

    private List<InputDevice> getEnabledNonTouchInputDevices(int source) {
        final InputManagerGlobal inputManager = InputManagerGlobal.getInstance();
        final int[] inputIds = inputManager.getInputDeviceIds();
        List<InputDevice> matchingDevices = new ArrayList<InputDevice>();
        for (final int deviceId : inputIds) {
            final InputDevice inputDevice = inputManager.getInputDevice(deviceId);
            if (!inputDevice.isEnabled()) continue;
            if (inputDevice.supportsSource(InputDevice.SOURCE_TOUCHSCREEN)) continue;
            if (inputDevice.isVirtual()) continue;
            if (!inputDevice.supportsSource(source)) continue;
            matchingDevices.add(inputDevice);
        }
        return matchingDevices;
    }

    private boolean hasPhysicalKeyboardActive() {
        final List<InputDevice> keyboards =
                getEnabledNonTouchInputDevices(InputDevice.SOURCE_KEYBOARD);
        for (final InputDevice keyboard : keyboards) {
            if (keyboard.isFullKeyboard()) return true;
        }
        return false;
    }

    /**
     * @return Whether enhanced pin privacy is enabled.
     */
    public boolean isPinEnhancedPrivacyEnabled(int userId) {
        boolean defaultValue = false;
        if (hideLastCharWithPhysicalInput()) {
            defaultValue = hasPhysicalKeyboardActive();
        if (useDefaultVisibilityForSensitiveInputs()) {
            defaultValue =
                    mContext.getResources()
                            .getBoolean(
                                    com.android.internal.R.bool
                                            .config_lockPinEnhancedPrivacyDefault);
        }
        return getBoolean(LOCK_PIN_ENHANCED_PRIVACY, defaultValue, userId);
    }
+10 −0
Original line number Diff line number Diff line
@@ -14,3 +14,13 @@ flag {
    description: "Feature flag for changing the priority of the check credential task"
    bug: "407716215"
}

flag {
    name: "use_default_visibility_for_sensitive_inputs"
    namespace: "input"
    description: "Allows setting default value for visibility of sensitive inputs (passowrd, pin and pattern)."
    bug: "339270220"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -1537,6 +1537,17 @@
         wrong guesses, improving usability. -->
    <bool name="config_softwareLskfRateLimiterEnforcing">false</bool>

    <!-- Default value of the setting that controls whether animations should be disabled while
         entering the PIN on the lockscreen.
         False means animations should be enabled by default.
         True means animations should be disabled by default. -->
    <bool name="config_lockPinEnhancedPrivacyDefault">false</bool>

    <!-- Default value for the setting that controls whether lock pattern is visible as user enters.
         True means the pattern should be visible by default.
         False means the pattern should not be visible by default. -->
    <bool name="config_lockPatternVisibleDefault">true</bool>

    <!-- Control the behavior when the user long presses the home button.
            0 - Nothing
            1 - Launch all apps intent
+3 −0
Original line number Diff line number Diff line
@@ -4196,6 +4196,9 @@
  <java-symbol type="bool" name="config_disableWeaverOnUnsecuredUsers" />
  <java-symbol type="bool" name="config_softwareLskfRateLimiterEnforcing" />

  <java-symbol type="bool" name="config_lockPinEnhancedPrivacyDefault" />
  <java-symbol type="bool" name="config_lockPatternVisibleDefault" />

  <!-- ETWS primary messages -->
  <java-symbol type="string" name="etws_primary_default_message_earthquake" />
  <java-symbol type="string" name="etws_primary_default_message_tsunami" />
+40 −130
Original line number Diff line number Diff line
@@ -47,8 +47,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.UserInfo;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerGlobal;
import android.content.res.Resources;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -60,7 +59,6 @@ import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.view.InputDevice;

import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -95,6 +93,8 @@ public class LockPatternUtilsTest {

    private LockPatternUtils mLockPatternUtils;

    private Resources mResources;

    private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
            throws Exception {
        mLockSettings = mock(ILockSettings.class);
@@ -458,155 +458,65 @@ public class LockPatternUtilsTest {
        };
    }

    private InputManagerGlobal.TestSession configureExternalHardwareTest(InputDevice[] devices)
            throws RemoteException {
        final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
    private void configureSensitiveInputVisibilityTest() throws RemoteException {
        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
        final ILockSettings ils = mock(ILockSettings.class);
        when(ils.getBoolean(anyString(), anyBoolean(), anyInt())).thenThrow(RemoteException.class);
        mLockPatternUtils = new LockPatternUtils(context, ils);

        IInputManager inputManagerMock = mock(IInputManager.class);

        int[] deviceIds = new int[devices.length];

        for (int i = 0; i < devices.length; i++) {
            when(inputManagerMock.getInputDevice(i)).thenReturn(devices[i]);
        }

        when(inputManagerMock.getInputDeviceIds()).thenReturn(deviceIds);
        InputManagerGlobal.TestSession session =
                InputManagerGlobal.createTestSession(inputManagerMock);

        return session;
    }

    @Test
    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isPinEnhancedPrivacyEnabled_noDevicesAttached() throws RemoteException {
        InputManagerGlobal.TestSession session = configureExternalHardwareTest(new InputDevice[0]);
        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
        session.close();
    }

    @Test
    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isPinEnhancedPrivacyEnabled_noEnabledDeviceAttached() throws RemoteException {
        InputDevice.Builder builder = new InputDevice.Builder();
        builder.setEnabled(false);
        InputManagerGlobal.TestSession session =
                configureExternalHardwareTest(new InputDevice[]{builder.build()});
        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
        session.close();
        mResources = spy(context.getResources());
        when(context.getResources()).thenReturn(mResources);
    }

    @Test
    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isPinEnhancedPrivacyEnabled_withoutHwKeyboard() throws RemoteException {
        InputDevice.Builder builder = new InputDevice.Builder();
        builder.setEnabled(true).setSources(InputDevice.SOURCE_TOUCHSCREEN);
        InputManagerGlobal.TestSession session =
                configureExternalHardwareTest(new InputDevice[]{builder.build()});
        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
        session.close();
    }

    @Test
    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isPinEnhancedPrivacyEnabled_withoutFullHwKeyboard() throws RemoteException {
        InputDevice.Builder builder = new InputDevice.Builder();
        builder
                .setEnabled(true)
                .setSources(InputDevice.SOURCE_KEYBOARD)
                .setKeyboardType(InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC);
        InputManagerGlobal.TestSession session =
                configureExternalHardwareTest(new InputDevice[]{builder.build()});
        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
        session.close();
    }

    @Test
    @DisableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isPinEnhancedPrivacyEnabled_withHwKeyboardOldDefault() throws RemoteException {
        InputDevice.Builder builder = new InputDevice.Builder();
        builder
                .setEnabled(true)
                .setSources(InputDevice.SOURCE_KEYBOARD)
                .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC);
        InputManagerGlobal.TestSession session =
                configureExternalHardwareTest(new InputDevice[]{builder.build()});
        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
        session.close();
    }

    @Test
    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isPinEnhancedPrivacyEnabled_withHwKeyboard() throws RemoteException {
        InputDevice.Builder builder = new InputDevice.Builder();
        builder
                .setEnabled(true)
                .setSources(InputDevice.SOURCE_KEYBOARD)
                .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC);
        InputManagerGlobal.TestSession session =
                configureExternalHardwareTest(new InputDevice[]{builder.build()});
        assertTrue(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
        session.close();
    @EnableFlags(Flags.FLAG_USE_DEFAULT_VISIBILITY_FOR_SENSITIVE_INPUTS)
    public void isVisiblePatternEnabled_WhenConfigValueTrue() throws RemoteException {
        configureSensitiveInputVisibilityTest();
        when(mResources.getBoolean(com.android.internal.R.bool.config_lockPatternVisibleDefault))
                .thenReturn(true);
        assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
    }

    @Test
    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isVisiblePatternEnabled_noDevices() throws RemoteException {
        InputManagerGlobal.TestSession session = configureExternalHardwareTest(new InputDevice[0]);
        assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
        session.close();
    @EnableFlags(Flags.FLAG_USE_DEFAULT_VISIBILITY_FOR_SENSITIVE_INPUTS)
    public void isVisiblePatternEnabled_WhenConfigValueFalse() throws RemoteException {
        configureSensitiveInputVisibilityTest();
        when(mResources.getBoolean(com.android.internal.R.bool.config_lockPatternVisibleDefault))
                .thenReturn(false);
        assertFalse(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
    }

    @Test
    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isVisiblePatternEnabled_noEnabledDevices() throws RemoteException {
        InputDevice.Builder builder = new InputDevice.Builder();
        builder.setEnabled(false);
        InputManagerGlobal.TestSession session =
                configureExternalHardwareTest(new InputDevice[]{builder.build()});
    @DisableFlags(Flags.FLAG_USE_DEFAULT_VISIBILITY_FOR_SENSITIVE_INPUTS)
    public void isVisiblePatternEnabled_OldDefault() throws RemoteException {
        configureSensitiveInputVisibilityTest();
        assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
        session.close();
    }

    @Test
    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isVisiblePatternEnabled_noPointingDevices() throws RemoteException {
        InputDevice.Builder builder = new InputDevice.Builder();
        builder
                .setEnabled(true)
                .setSources(InputDevice.SOURCE_TOUCHSCREEN);
        InputManagerGlobal.TestSession session =
                configureExternalHardwareTest(new InputDevice[]{builder.build()});
        assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
        session.close();
    @EnableFlags(Flags.FLAG_USE_DEFAULT_VISIBILITY_FOR_SENSITIVE_INPUTS)
    public void isPinEnhancedPrivacyEnabled_WhenConfigValueTrue() throws RemoteException {
        configureSensitiveInputVisibilityTest();
        when(mResources.getBoolean(
                        com.android.internal.R.bool.config_lockPinEnhancedPrivacyDefault))
                .thenReturn(true);
        assertTrue(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
    }

    @Test
    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isVisiblePatternEnabled_externalPointingDevice() throws RemoteException {
        InputDevice.Builder builder = new InputDevice.Builder();
        builder
                .setEnabled(true)
                .setSources(InputDevice.SOURCE_CLASS_POINTER);
        InputManagerGlobal.TestSession session =
                configureExternalHardwareTest(new InputDevice[]{builder.build()});
        assertFalse(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
        session.close();
    @EnableFlags(Flags.FLAG_USE_DEFAULT_VISIBILITY_FOR_SENSITIVE_INPUTS)
    public void isPinEnhancedPrivacyEnabled_WhenConfigValueFalse() throws RemoteException {
        configureSensitiveInputVisibilityTest();
        when(mResources.getBoolean(
                        com.android.internal.R.bool.config_lockPinEnhancedPrivacyDefault))
                .thenReturn(false);
        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
    }

    @Test
    @DisableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
    public void isVisiblePatternEnabled_externalPointingDeviceOldDefault() throws RemoteException {
        InputDevice.Builder builder = new InputDevice.Builder();
        builder
                .setEnabled(true)
                .setSources(InputDevice.SOURCE_CLASS_POINTER);
        InputManagerGlobal.TestSession session =
                configureExternalHardwareTest(new InputDevice[]{builder.build()});
        assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
        session.close();
    @DisableFlags(Flags.FLAG_USE_DEFAULT_VISIBILITY_FOR_SENSITIVE_INPUTS)
    public void isPinEnhancedPrivacyEnabled_OldDefault() throws RemoteException {
        configureSensitiveInputVisibilityTest();
        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
    }
}