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

Commit 931c8c70 authored by Joe Bolinger's avatar Joe Bolinger Committed by Automerger Merge Worker
Browse files

Merge "Add a final lux method to ALSProbe." into tm-qpr-dev am: 95412931 am: b9c39df0

parents 425a324a b9c39df0
Loading
Loading
Loading
Loading
+95 −5
Original line number Diff line number Diff line
@@ -30,7 +30,10 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.BaseClientMonitor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/** Probe for ambient light. */
final class ALSProbe implements Probe {
@@ -47,12 +50,18 @@ final class ALSProbe implements Probe {

    private boolean mEnabled = false;
    private boolean mDestroyed = false;
    private boolean mDestroyRequested = false;
    private boolean mDisableRequested = false;
    private volatile NextConsumer mNextConsumer = null;
    private volatile float mLastAmbientLux = -1;

    private final SensorEventListener mLightSensorListener = new SensorEventListener() {
        @Override
        public void onSensorChanged(SensorEvent event) {
            mLastAmbientLux = event.values[0];
            if (mNextConsumer != null) {
                completeNextConsumer(mLastAmbientLux);
            }
        }

        @Override
@@ -102,29 +111,84 @@ final class ALSProbe implements Probe {

    @Override
    public synchronized void enable() {
        if (!mDestroyed) {
        if (!mDestroyed && !mDestroyRequested) {
            mDisableRequested = false;
            enableLightSensorLoggingLocked();
        }
    }

    @Override
    public synchronized void disable() {
        if (!mDestroyed) {
        mDisableRequested = true;

        // if a final consumer is set it will call destroy/disable on the next value if requested
        if (!mDestroyed && mNextConsumer == null) {
            disableLightSensorLoggingLocked();
        }
    }

    @Override
    public synchronized void destroy() {
        mDestroyRequested = true;

        // if a final consumer is set it will call destroy/disable on the next value if requested
        if (!mDestroyed && mNextConsumer == null) {
            disable();
            mDestroyed = true;
        }
    }

    /** The most recent lux reading. */
    public float getCurrentLux() {
    public float getMostRecentLux() {
        return mLastAmbientLux;
    }

    /**
     * Register a listener for the next available ALS reading, which will be reported to the given
     * consumer even if this probe is {@link #disable()}'ed or {@link #destroy()}'ed before a value
     * is available.
     *
     * This method is intended to be used for event logs that occur when the screen may be
     * off and sampling may have been {@link #disable()}'ed. In these cases, this method will turn
     * on the sensor (if needed), fetch & report the first value, and then destroy or disable this
     * probe (if needed).
     *
     * @param consumer consumer to notify when the data is available
     * @param handler handler for notifying the consumer, or null
     */
    public synchronized void awaitNextLux(@NonNull Consumer<Float> consumer,
            @Nullable Handler handler) {
        final NextConsumer nextConsumer = new NextConsumer(consumer, handler);
        final float current = mLastAmbientLux;
        if (current > 0) {
            nextConsumer.consume(current);
        } else if (mDestroyed) {
            nextConsumer.consume(-1f);
        } else if (mNextConsumer != null) {
            mNextConsumer.add(nextConsumer);
        } else {
            mNextConsumer = nextConsumer;
            enableLightSensorLoggingLocked();
        }
    }

    private synchronized void completeNextConsumer(float value) {
        Slog.v(TAG, "Finishing next consumer");

        final NextConsumer consumer = mNextConsumer;
        mNextConsumer = null;

        if (mDestroyRequested) {
            destroy();
        } else if (mDisableRequested) {
            disable();
        }

        if (consumer != null) {
            consumer.consume(value);
        }
    }

    private void enableLightSensorLoggingLocked() {
        if (!mEnabled) {
            mEnabled = true;
@@ -160,4 +224,30 @@ final class ALSProbe implements Probe {
                + mLightSensorListener.hashCode());
        disable();
    }

    private static class NextConsumer {
        @NonNull private final Consumer<Float> mConsumer;
        @Nullable private final Handler mHandler;
        @NonNull private final List<NextConsumer> mOthers = new ArrayList<>();

        private NextConsumer(@NonNull Consumer<Float> consumer, @Nullable Handler handler) {
            mConsumer = consumer;
            mHandler = handler;
        }

        public void consume(float value) {
            if (mHandler != null) {
                mHandler.post(() -> mConsumer.accept(value));
            } else {
                mConsumer.accept(value);
            }
            for (NextConsumer c : mOthers) {
                c.consume(value);
            }
        }

        public void add(NextConsumer consumer) {
            mOthers.add(consumer);
        }
    }
}
+11 −2
Original line number Diff line number Diff line
@@ -62,8 +62,7 @@ public class BiometricFrameworkStatsLogger {
    /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
    public void authenticate(OperationContext operationContext,
            int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
            int authState, boolean requireConfirmation,
            int targetUserId, float ambientLightLux) {
            int authState, boolean requireConfirmation, int targetUserId, float ambientLightLux) {
        FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
                statsModality,
                targetUserId,
@@ -80,6 +79,16 @@ public class BiometricFrameworkStatsLogger {
                operationContext.isAod);
    }

    /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
    public void authenticate(OperationContext operationContext,
            int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
            int authState, boolean requireConfirmation, int targetUserId, ALSProbe alsProbe) {
        alsProbe.awaitNextLux((ambientLightLux) -> {
            authenticate(operationContext, statsModality, statsAction, statsClient, isDebug,
                    latency, authState, requireConfirmation, targetUserId, ambientLightLux);
        }, null /* handler */);
    }

    /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
    public void enroll(int statsModality, int statsAction, int statsClient,
            int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) {
+4 −4
Original line number Diff line number Diff line
@@ -220,7 +220,7 @@ public class BiometricLogger {
                    + ", RequireConfirmation: " + requireConfirmation
                    + ", State: " + authState
                    + ", Latency: " + latency
                    + ", Lux: " + mALSProbe.getCurrentLux());
                    + ", Lux: " + mALSProbe.getMostRecentLux());
        } else {
            Slog.v(TAG, "Authentication latency: " + latency);
        }
@@ -231,7 +231,7 @@ public class BiometricLogger {

        mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient,
                Utils.isDebugEnabled(context, targetUserId),
                latency, authState, requireConfirmation, targetUserId, mALSProbe.getCurrentLux());
                latency, authState, requireConfirmation, targetUserId, mALSProbe);
    }

    /** Log enrollment outcome. */
@@ -245,7 +245,7 @@ public class BiometricLogger {
                    + ", User: " + targetUserId
                    + ", Client: " + mStatsClient
                    + ", Latency: " + latency
                    + ", Lux: " + mALSProbe.getCurrentLux()
                    + ", Lux: " + mALSProbe.getMostRecentLux()
                    + ", Success: " + enrollSuccessful);
        } else {
            Slog.v(TAG, "Enroll latency: " + latency);
@@ -256,7 +256,7 @@ public class BiometricLogger {
        }

        mSink.enroll(mStatsModality, mStatsAction, mStatsClient,
                targetUserId, latency, enrollSuccessful, mALSProbe.getCurrentLux());
                targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux());
    }

    /** Report unexpected enrollment reported by the HAL. */
+3 −0
Original line number Diff line number Diff line
@@ -333,6 +333,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
                mALSProbeCallback.getProbe().disable();
            }
        });
        if (getBiometricContext().isAwake()) {
            mALSProbeCallback.getProbe().enable();
        }

        if (session.hasContextMethods()) {
            return session.getSession().authenticateWithContext(mOperationId, opContext);
+154 −5
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -50,6 +51,9 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Presubmit
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -93,7 +97,7 @@ public class ALSProbeTest {
        mSensorEventListenerCaptor.getValue().onSensorChanged(
                new SensorEvent(mLightSensor, 1, 2, new float[]{value}));

        assertThat(mProbe.getCurrentLux()).isEqualTo(value);
        assertThat(mProbe.getMostRecentLux()).isEqualTo(value);
    }

    @Test
@@ -121,13 +125,17 @@ public class ALSProbeTest {
        mProbe.destroy();
        mProbe.enable();

        AtomicInteger lux = new AtomicInteger(10);
        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);

        verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
        verifyNoMoreInteractions(mSensorManager);
        assertThat(lux.get()).isLessThan(0);
    }

    @Test
    public void testDisabledReportsNegativeValue() {
        assertThat(mProbe.getCurrentLux()).isLessThan(0f);
        assertThat(mProbe.getMostRecentLux()).isLessThan(0f);

        mProbe.enable();
        verify(mSensorManager).registerListener(
@@ -136,7 +144,7 @@ public class ALSProbeTest {
                new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f}));
        mProbe.disable();

        assertThat(mProbe.getCurrentLux()).isLessThan(0f);
        assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
    }

    @Test
@@ -150,7 +158,7 @@ public class ALSProbeTest {

        verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
        verifyNoMoreInteractions(mSensorManager);
        assertThat(mProbe.getCurrentLux()).isLessThan(0f);
        assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
    }

    @Test
@@ -166,7 +174,148 @@ public class ALSProbeTest {

        verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
        verifyNoMoreInteractions(mSensorManager);
        assertThat(mProbe.getCurrentLux()).isLessThan(0f);
        assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
    }

    @Test
    public void testNextLuxWhenAlreadyEnabledAndNotAvailable() {
        testNextLuxWhenAlreadyEnabled(false /* dataIsAvailable */);
    }

    @Test
    public void testNextLuxWhenAlreadyEnabledAndAvailable() {
        testNextLuxWhenAlreadyEnabled(true /* dataIsAvailable */);
    }

    private void testNextLuxWhenAlreadyEnabled(boolean dataIsAvailable) {
        final List<Integer> values = List.of(1, 2, 3, 4, 6);
        mProbe.enable();

        verify(mSensorManager).registerListener(
                mSensorEventListenerCaptor.capture(), any(), anyInt());

        if (dataIsAvailable) {
            for (int v : values) {
                mSensorEventListenerCaptor.getValue().onSensorChanged(
                        new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
            }
        }
        AtomicInteger lux = new AtomicInteger(-1);
        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
        if (!dataIsAvailable) {
            for (int v : values) {
                mSensorEventListenerCaptor.getValue().onSensorChanged(
                        new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
            }
        }

        mSensorEventListenerCaptor.getValue().onSensorChanged(
                new SensorEvent(mLightSensor, 1, 1, new float[]{200f}));

        // should remain enabled
        assertThat(lux.get()).isEqualTo(values.get(dataIsAvailable ? values.size() - 1 : 0));
        verify(mSensorManager, never()).unregisterListener(any(SensorEventListener.class));
        verifyNoMoreInteractions(mSensorManager);

        final int anotherValue = 12;
        mSensorEventListenerCaptor.getValue().onSensorChanged(
                new SensorEvent(mLightSensor, 1, 1, new float[]{12}));
        assertThat(mProbe.getMostRecentLux()).isEqualTo(anotherValue);
    }

    @Test
    public void testNextLuxWhenNotEnabled() {
        testNextLuxWhenNotEnabled(false /* enableWhileWaiting */);
    }

    @Test
    public void testNextLuxWhenNotEnabledButEnabledLater() {
        testNextLuxWhenNotEnabled(true /* enableWhileWaiting */);
    }

    private void testNextLuxWhenNotEnabled(boolean enableWhileWaiting) {
        final List<Integer> values = List.of(1, 2, 3, 4, 6);
        mProbe.disable();

        AtomicInteger lux = new AtomicInteger(-1);
        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);

        if (enableWhileWaiting) {
            mProbe.enable();
        }

        verify(mSensorManager).registerListener(
                mSensorEventListenerCaptor.capture(), any(), anyInt());
        for (int v : values) {
            mSensorEventListenerCaptor.getValue().onSensorChanged(
                    new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
        }

        // should restore the disabled state
        assertThat(lux.get()).isEqualTo(values.get(0));
        verify(mSensorManager, enableWhileWaiting ? never() : times(1)).unregisterListener(
                any(SensorEventListener.class));
        verifyNoMoreInteractions(mSensorManager);
    }

    @Test
    public void testNextLuxIsNotCanceledByDisableOrDestroy() {
        final int value = 7;
        AtomicInteger lux = new AtomicInteger(-1);
        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);

        verify(mSensorManager).registerListener(
                mSensorEventListenerCaptor.capture(), any(), anyInt());

        mProbe.destroy();
        mProbe.disable();

        assertThat(lux.get()).isEqualTo(-1);

        mSensorEventListenerCaptor.getValue().onSensorChanged(
                new SensorEvent(mLightSensor, 1, 1, new float[]{value}));

        assertThat(lux.get()).isEqualTo(value);

        mSensorEventListenerCaptor.getValue().onSensorChanged(
                new SensorEvent(mLightSensor, 1, 1, new float[]{value + 1}));

        // should remain destroyed
        mProbe.enable();

        assertThat(lux.get()).isEqualTo(value);
        verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
        verifyNoMoreInteractions(mSensorManager);
    }

    @Test
    public void testMultipleNextConsumers() {
        final int value = 7;
        AtomicInteger lux = new AtomicInteger(-1);
        AtomicInteger lux2 = new AtomicInteger(-1);
        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
        mProbe.awaitNextLux((v) -> lux2.set(Math.round(v)), null /* handler */);

        verify(mSensorManager).registerListener(
                mSensorEventListenerCaptor.capture(), any(), anyInt());
        mSensorEventListenerCaptor.getValue().onSensorChanged(
                new SensorEvent(mLightSensor, 1, 1, new float[]{value}));

        assertThat(lux.get()).isEqualTo(value);
        assertThat(lux2.get()).isEqualTo(value);
    }

    @Test
    public void testNoNextLuxWhenDestroyed() {
        mProbe.destroy();

        AtomicInteger lux = new AtomicInteger(-20);
        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);

        assertThat(lux.get()).isEqualTo(-1);
        verify(mSensorManager, never()).registerListener(
                mSensorEventListenerCaptor.capture(), any(), anyInt());
        verifyNoMoreInteractions(mSensorManager);
    }

    private void moveTimeBy(long millis) {
Loading