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

Commit 7ed25b9b authored by Shawn Lin's avatar Shawn Lin
Browse files

Support logging biometrics onboarding metrics

- Add BiometricsLogger used for logging metrics in
  BiometricsFeatureProvider
- Add OnboardingEvent that represents the SettingsBiometricsOnboarding
  proto message
- Add OnboardingScreenInfoEvent that represents the OnboardingScreenInfo
  prot message

Bug: 370940210
Test: make
Flag: com.android.settings.flags.biometrics_onboarding_education

Change-Id: I9fa6a858c156cae42c1680f51171959f2746fd74
parent cfeeaf78
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -55,6 +55,8 @@ import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.Utils;
import com.android.settings.biometrics.combination.CombinedBiometricStatusUtils;
import com.android.settings.biometrics.metrics.BiometricsLogger;
import com.android.settings.biometrics.metrics.OnboardingEvent;
import com.android.settings.core.InstrumentedActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockGeneric;
@@ -65,6 +67,7 @@ import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.transition.TransitionHelper;

import java.util.ArrayList;
import java.util.List;

/**
@@ -139,6 +142,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
    @Nullable private ParentalConsentHelper mParentalConsentHelper;
    private boolean mIsPreviousEnrollmentCanceled = false;

    private List<OnboardingEvent> mOnboardingEvents = new ArrayList<>();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
@@ -527,6 +532,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
        Log.d(TAG, "handleOnActivityResultWhileEnrolling, request = " + requestCode + ""
                + ", resultCode = " + resultCode + ", launchFaceEnrollFirst="
                + mLaunchFaceEnrollFirst);
        updateOnboardingEventList(data);
        switch (requestCode) {
            case REQUEST_HANDOFF_PARENT:
                setResult(RESULT_OK, newResultIntent());
@@ -648,6 +654,14 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
        if (mPassThroughExtrasFromChosenLockInSuw != null) {
            intent.putExtras(mPassThroughExtrasFromChosenLockInSuw);
        }
        final BiometricsLogger logger = FeatureFactory.getFeatureFactory()
                .getBiometricsFeatureProvider().getBiometricsLogger();
        if (logger != null && !mOnboardingEvents.isEmpty()) {
            intent.putExtra(
                    BiometricsLogger.EXTRA_BIOMETRICS_ONBOARDING_EVENT_BYTES_LIST,
                    logger.eventListToRepeatedMessageByteArray(mOnboardingEvents)
            );
        }
        return intent;
    }

@@ -766,6 +780,12 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
            } else {
                intent = BiometricUtils.getFingerprintIntroIntent(this, getIntent());
            }
            if (getIntent().getBooleanExtra(
                    CombinedBiometricStatusUtils.EXTRA_LAUNCH_FROM_SAFETY_SOURCE_ISSUE, false)) {
                intent.putExtra(
                        CombinedBiometricStatusUtils.EXTRA_LAUNCH_FROM_SAFETY_SOURCE_ISSUE,
                        true);
            }
            launchSingleSensorEnrollActivity(intent, REQUEST_SINGLE_ENROLL_FINGERPRINT);
        }
    }
@@ -774,6 +794,12 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
        if (!mIsSingleEnrolling) {
            mIsSingleEnrolling = true;
            final Intent intent = BiometricUtils.getFaceIntroIntent(this, getIntent());
            if (getIntent().getBooleanExtra(
                    CombinedBiometricStatusUtils.EXTRA_LAUNCH_FROM_SAFETY_SOURCE_ISSUE, false)) {
                intent.putExtra(
                        CombinedBiometricStatusUtils.EXTRA_LAUNCH_FROM_SAFETY_SOURCE_ISSUE,
                        true);
            }
            launchSingleSensorEnrollActivity(intent, REQUEST_SINGLE_ENROLL_FACE);
        }
    }
@@ -796,4 +822,15 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
    public int getMetricsCategory() {
        return SettingsEnums.BIOMETRIC_ENROLL_ACTIVITY;
    }

    private void updateOnboardingEventList(Intent data) {
        if (data != null && data.hasExtra(BiometricsLogger.EXTRA_BIOMETRICS_ONBOARDING_EVENT)) {
            final OnboardingEvent event = data.getParcelableExtra(
                    BiometricsLogger.EXTRA_BIOMETRICS_ONBOARDING_EVENT, OnboardingEvent.class);
            mOnboardingEvents.add(event);
            if (BiometricsLogger.LOGGABLE) {
                Log.d(BiometricsLogger.TAG, getClass().getSimpleName() + ": add Event:" + event);
            }
        }
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.safetycenter.SafetySourceIssue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.settings.biometrics.metrics.BiometricsLogger;

public interface BiometricsFeatureProvider {

    /** Returns a SafetySourceIssue for biometrics. */
@@ -29,4 +31,8 @@ public interface BiometricsFeatureProvider {

    /** Notifies that the action of an issue is launched */
    void notifySafetyIssueActionLaunched();

    /** Return logger for biometrics */
    @Nullable
    BiometricsLogger getBiometricsLogger();
}
+8 −0
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.safetycenter.SafetySourceIssue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.settings.biometrics.metrics.BiometricsLogger;

public class BiometricsFeatureProviderImpl implements BiometricsFeatureProvider {
    @Nullable
    @Override
@@ -30,4 +32,10 @@ public class BiometricsFeatureProviderImpl implements BiometricsFeatureProvider

    @Override
    public void notifySafetyIssueActionLaunched() {}

    @Nullable
    @Override
    public BiometricsLogger getBiometricsLogger() {
        return null;
    }
}
+63 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.settings.biometrics.metrics;

import android.annotation.Nullable;
import android.os.Build;

import androidx.annotation.NonNull;

import java.util.List;

/**
 * Logger class for biometrics
 */
public interface BiometricsLogger {
    String TAG = "BiometricsLogger";
    Boolean LOGGABLE = Build.isDebuggable();

    /** The bundle extra key for BiometricsOnboardingEvent used in Face/Fingerprint Enroll */
    String EXTRA_BIOMETRICS_ONBOARDING_EVENT = "biometrics_onboarding_event";

    /**
     * The bundle extra key for BiometricsOnboardingEvent in bytes array used in Face Enroll.
     */
    String EXTRA_BIOMETRICS_ONBOARDING_EVENT_BYTES = "biometrics_onboarding_event_bytes";

    /**
     * The bundle extra key for a list of BiometricsOnboardingEvent in byte array used in
     * Face /Fingerprint Enroll
     */
    String EXTRA_BIOMETRICS_ONBOARDING_EVENT_BYTES_LIST = "biometrics_onboarding_event_bytes_list";

    /** Log SettingsBiometricsOnboarding metrics */
    void logSettingsBiometricsOnboarding(@NonNull OnboardingEvent event);

    /** Convert BiometricOnboardingEvent to proto message byte array */
    byte[] eventToMessageByteArray(@NonNull OnboardingEvent event);

    /**
     * Convert proto message byte array to BiometricsOnboardingEvent.
     */
    @Nullable
    OnboardingEvent messageByteArrayToEvent(byte[] message);

    /**
     * Convert list of BiometricOnboardingEvent to repeated message byte array.
     */
    byte[] eventListToRepeatedMessageByteArray(@NonNull List<OnboardingEvent> events);
}
+210 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.settings.biometrics.metrics;

import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

import com.android.settings.biometrics.BiometricsOnboardingProto;
import com.android.settings.biometrics.BiometricsOnboardingProto.OnboardingScreenInfo;
import com.android.settings.biometrics.BiometricsOnboardingProto.SettingsBiometricsOnboarding;

import java.util.ArrayList;
import java.util.List;

/**
 * Represents SettingsBiometricsOnboarding proto message.
 * See Settings/proto/biometrics_onboarding.proto.
 */
public class OnboardingEvent implements Parcelable {
    private int mModality;
    private int mFromSource;
    private int mUserId;
    private int mEnrolledCount;
    private long mDuration;
    private int mCapybaraStatus;
    private int mResultCode;
    private int mErrorCode;
    private List<OnboardingScreenInfoEvent> mScreenInfos = new ArrayList<>();

    public OnboardingEvent() {
    }

    public OnboardingEvent(SettingsBiometricsOnboarding message) {
        mModality = message.getModality().getNumber();
        mFromSource = message.getFromSource().getNumber();
        mUserId = message.getUser();
        mEnrolledCount = message.getEnrolledCount();
        mDuration = message.getDurationMillis();
        mCapybaraStatus = message.getCapybaraStatus();
        mResultCode = message.getResultCode().getNumber();
        mErrorCode = message.getErrorCode();
        final List<OnboardingScreenInfo> infoList =
                message.getOnboardingScreenInfoList().getInfoListList();
        for (OnboardingScreenInfo info : infoList) {
            mScreenInfos.add(new OnboardingScreenInfoEvent(
                    info.getOnboardingScreen().getNumber(),
                    info.getDwellTimeMillis(),
                    info.getOnboardingActionsList().stream().mapToInt(
                            BiometricsOnboardingProto.OnboardingAction::getNumber).toArray()
            ));
        }
    }

    protected OnboardingEvent(Parcel in) {
        mModality = in.readInt();
        mFromSource = in.readInt();
        mUserId = in.readInt();
        mEnrolledCount = in.readInt();
        mDuration = in.readLong();
        mCapybaraStatus = in.readInt();
        mResultCode = in.readInt();
        mErrorCode = in.readInt();
        in.readTypedList(mScreenInfos, OnboardingScreenInfoEvent.CREATOR);
    }

    public int getModality() {
        return mModality;
    }

    public void setModality(int modality) {
        mModality = modality;
    }

    public int getFromSource() {
        return mFromSource;
    }

    public void setFromSource(int fromSource) {
        mFromSource = fromSource;
    }

    public int getUserId() {
        return mUserId;
    }

    public void setUserId(int userId) {
        mUserId = userId;
    }

    public int getEnrolledCount() {
        return mEnrolledCount;
    }

    public void setEnrolledCount(int enrolledCount) {
        mEnrolledCount = enrolledCount;
    }

    public long getDuration() {
        return mDuration;
    }

    public void setDuration(long duration) {
        mDuration = duration;
    }

    public int getCapybaraStatus() {
        return mCapybaraStatus;
    }

    public void setCapybaraStatus(int capybaraStatus) {
        mCapybaraStatus = capybaraStatus;
    }

    public int getResultCode() {
        return mResultCode;
    }

    public void setResultCode(int resultCode) {
        mResultCode = resultCode;
    }

    public int getErrorCode() {
        return mErrorCode;
    }

    public void setErrorCode(int errorCode) {
        mErrorCode = errorCode;
    }

    @NonNull
    public List<OnboardingScreenInfoEvent> getScreenInfos() {
        return mScreenInfos;
    }

    /** Add screen info */
    public void addScreenInfo(@NonNull OnboardingScreenInfoEvent screenInfo) {
        mScreenInfos.add(screenInfo);
    }

    public static final Creator<OnboardingEvent> CREATOR = new Creator<>() {
        @Override
        public OnboardingEvent createFromParcel(Parcel in) {
            return new OnboardingEvent(in);
        }

        @Override
        public OnboardingEvent[] newArray(int size) {
            return new OnboardingEvent[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mModality);
        dest.writeInt(mFromSource);
        dest.writeInt(mUserId);
        dest.writeInt(mEnrolledCount);
        dest.writeLong(mDuration);
        dest.writeInt(mCapybaraStatus);
        dest.writeInt(mResultCode);
        dest.writeInt(mErrorCode);
        dest.writeTypedList(mScreenInfos);
    }

    @Override
    public String toString() {
        return "BiometricsOnboardingEvent{"
                + "mModality=" + mModality
                + ", mSource=" + mFromSource
                + ", mUserId=" + mUserId
                + ", mEnrolledCount=" + mEnrolledCount
                + ", mTotalTime=" + mDuration
                + ", mCapybaraStatus=" + mCapybaraStatus
                + ", mResultCode=" + mResultCode
                + ", mErrorCode=" + mErrorCode
                + ", mScreenInfos=" + screenInfosToString()
                + "}";
    }

    private String screenInfosToString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("{");
        for (OnboardingScreenInfoEvent screenInfo : mScreenInfos) {
            sb.append(screenInfo.toString());
            sb.append(", ");
        }
        return sb.append("}").toString();
    }
}
Loading