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

Commit 584af379 authored by Angela Wang's avatar Angela Wang Committed by Android (Google) Code Review
Browse files

Merge "Update categorization logic when updating history" into main

parents a090aa24 711ffac4
Loading
Loading
Loading
Loading
+55 −30
Original line number Diff line number Diff line
@@ -45,16 +45,25 @@ public final class HearingAidStatsLogUtils {
    private static final String TAG = "HearingAidStatsLogUtils";
    private static final boolean DEBUG = true;
    private static final String ACCESSIBILITY_PREFERENCE = "accessibility_prefs";
    private static final String BT_HEARING_AIDS_PAIRED_HISTORY = "bt_hearing_aids_paired_history";
    private static final String BT_HEARING_AIDS_CONNECTED_HISTORY =

    private static final String BT_HEARING_DEVICES_PAIRED_HISTORY =
            "bt_hearing_aids_paired_history";
    private static final String BT_HEARING_DEVICES_CONNECTED_HISTORY =
            "bt_hearing_aids_connected_history";
    // The values here actually represent Bluetooth hearable devices, but were mistyped
    // as hearing devices in the string value previously. Keep the string values to ensure record
    // persistence.
    private static final String BT_HEARABLE_DEVICES_PAIRED_HISTORY =
            "bt_hearing_devices_paired_history";
    private static final String BT_HEARABLE_DEVICES_CONNECTED_HISTORY =
            "bt_hearing_devices_connected_history";

    private static final String HISTORY_RECORD_DELIMITER = ",";
    static final String CATEGORY_HEARING_AIDS = "A11yHearingAidsUser";
    static final String CATEGORY_NEW_HEARING_AIDS = "A11yNewHearingAidsUser";

    static final String CATEGORY_HEARING_DEVICES = "A11yHearingAidsUser";
    static final String CATEGORY_NEW_HEARING_DEVICES = "A11yNewHearingAidsUser";
    // Previously, 'hearable device' was incorrectly entered as 'hearing device'. These string
    // values are maintained to ensure records persistence.
    static final String CATEGORY_HEARABLE_DEVICES = "A11yHearingDevicesUser";
    static final String CATEGORY_NEW_HEARABLE_DEVICES = "A11yNewHearingDevicesUser";

@@ -69,14 +78,14 @@ public final class HearingAidStatsLogUtils {
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            HistoryType.TYPE_UNKNOWN,
            HistoryType.TYPE_HEARING_AIDS_PAIRED,
            HistoryType.TYPE_HEARING_AIDS_CONNECTED,
            HistoryType.TYPE_HEARING_DEVICES_PAIRED,
            HistoryType.TYPE_HEARING_DEVICES_CONNECTED,
            HistoryType.TYPE_HEARABLE_DEVICES_PAIRED,
            HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED})
    public @interface HistoryType {
        int TYPE_UNKNOWN = -1;
        int TYPE_HEARING_AIDS_PAIRED = 0;
        int TYPE_HEARING_AIDS_CONNECTED = 1;
        int TYPE_HEARING_DEVICES_PAIRED = 0;
        int TYPE_HEARING_DEVICES_CONNECTED = 1;
        int TYPE_HEARABLE_DEVICES_PAIRED = 2;
        int TYPE_HEARABLE_DEVICES_CONNECTED = 3;
    }
@@ -137,27 +146,25 @@ public final class HearingAidStatsLogUtils {
            LocalBluetoothProfile profile, int profileState) {

        if (isJustBonded(cachedDevice.getAddress())) {
            // Saves bonded timestamp as the source for judging whether to display
            // the survey
            if (cachedDevice.getProfiles().stream().anyMatch(
                    p -> (p instanceof HearingAidProfile || p instanceof HapClientProfile))) {
            // Saves bonded timestamp as the source for judging whether to display the survey
            if (isHearingDevice(cachedDevice)) {
                HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED);
            } else if (cachedDevice.getProfiles().stream().anyMatch(
                    p -> (p instanceof A2dpSinkProfile || p instanceof HeadsetProfile))) {
                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED);
            } else if (isHearableDevice(cachedDevice)) {
                HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_PAIRED);
            }
            removeFromJustBonded(cachedDevice.getAddress());
        }

        // Saves connected timestamp as the source for judging whether to display
        // the survey
        if (profileState == BluetoothProfile.STATE_CONNECTED) {
            if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
            // Saves connected timestamp as the source for judging whether to display the survey
            if (isHearingProfile(profile)) {
                HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED);
            } else if (profile instanceof A2dpSinkProfile || profile instanceof HeadsetProfile) {
                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED);
            } else if (isHearableProfile(profile) && !isHearingDevice(cachedDevice)) {
                // Hearing devices may contain some hearable profiles as well, make sure to exclude
                // these cases when update hearable connected history.
                HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED);
            }
@@ -166,8 +173,8 @@ public final class HearingAidStatsLogUtils {

    /**
     * Returns the user category if the user is already categorized. Otherwise, checks the
     * history and sees if the user is categorized as one of {@link #CATEGORY_HEARING_AIDS},
     * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARABLE_DEVICES}, and
     * history and sees if the user is categorized as one of {@link #CATEGORY_HEARING_DEVICES},
     * {@link #CATEGORY_NEW_HEARING_DEVICES}, {@link #CATEGORY_HEARABLE_DEVICES}, and
     * {@link #CATEGORY_NEW_HEARABLE_DEVICES}.
     *
     * @param context the request context
@@ -175,19 +182,19 @@ public final class HearingAidStatsLogUtils {
     */
    public static synchronized String getUserCategory(Context context) {
        LinkedList<Long> hearingAidsConnectedHistory = getHistory(context,
                HistoryType.TYPE_HEARING_AIDS_CONNECTED);
                HistoryType.TYPE_HEARING_DEVICES_CONNECTED);
        if (hearingAidsConnectedHistory != null
                && hearingAidsConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) {
            LinkedList<Long> hearingAidsPairedHistory = getHistory(context,
                    HistoryType.TYPE_HEARING_AIDS_PAIRED);
                    HistoryType.TYPE_HEARING_DEVICES_PAIRED);
            // Since paired history will be cleared after 30 days. If there's any record within 30
            // days, the user will be categorized as CATEGORY_NEW_HEARING_AIDS. Otherwise, the user
            // will be categorized as CATEGORY_HEARING_AIDS.
            if (hearingAidsPairedHistory != null
                    && hearingAidsPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) {
                return CATEGORY_NEW_HEARING_AIDS;
                return CATEGORY_NEW_HEARING_DEVICES;
            } else {
                return CATEGORY_HEARING_AIDS;
                return CATEGORY_HEARING_DEVICES;
            }
        }

@@ -271,13 +278,13 @@ public final class HearingAidStatsLogUtils {
    @Nullable
    static synchronized LinkedList<Long> getHistory(Context context, @HistoryType int type) {
        String spName = HISTORY_TYPE_TO_SP_NAME_MAPPING.get(type);
        if (BT_HEARING_AIDS_PAIRED_HISTORY.equals(spName)
        if (BT_HEARING_DEVICES_PAIRED_HISTORY.equals(spName)
                || BT_HEARABLE_DEVICES_PAIRED_HISTORY.equals(spName)) {
            LinkedList<Long> history = convertToHistoryList(
                    getSharedPreferences(context).getString(spName, ""));
            removeRecordsBeforeDay(history, PAIRED_HISTORY_EXPIRED_DAY);
            return history;
        } else if (BT_HEARING_AIDS_CONNECTED_HISTORY.equals(spName)
        } else if (BT_HEARING_DEVICES_CONNECTED_HISTORY.equals(spName)
                || BT_HEARABLE_DEVICES_CONNECTED_HISTORY.equals(spName)) {
            LinkedList<Long> history = convertToHistoryList(
                    getSharedPreferences(context).getString(spName, ""));
@@ -333,6 +340,23 @@ public final class HearingAidStatsLogUtils {
        return Math.abs(ChronoUnit.DAYS.between(date1, date2));
    }

    private static boolean isHearingDevice(CachedBluetoothDevice device) {
        return device.getProfiles().stream().anyMatch(HearingAidStatsLogUtils::isHearingProfile);
    }

    private static boolean isHearingProfile(LocalBluetoothProfile profile) {
        return profile instanceof HearingAidProfile || profile instanceof HapClientProfile;
    }

    private static boolean isHearableDevice(CachedBluetoothDevice device) {
        return device.getProfiles().stream().anyMatch(HearingAidStatsLogUtils::isHearableProfile);
    }

    private static boolean isHearableProfile(LocalBluetoothProfile profile) {
        return profile instanceof A2dpProfile || profile instanceof HeadsetProfile
                || profile instanceof LeAudioProfile;
    }

    private static SharedPreferences getSharedPreferences(Context context) {
        return context.getSharedPreferences(ACCESSIBILITY_PREFERENCE, Context.MODE_PRIVATE);
    }
@@ -341,13 +365,14 @@ public final class HearingAidStatsLogUtils {
    static {
        HISTORY_TYPE_TO_SP_NAME_MAPPING = new HashMap<>();
        HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
                HistoryType.TYPE_HEARING_AIDS_PAIRED, BT_HEARING_AIDS_PAIRED_HISTORY);
                HistoryType.TYPE_HEARING_DEVICES_PAIRED, BT_HEARING_DEVICES_PAIRED_HISTORY);
        HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
                HistoryType.TYPE_HEARING_AIDS_CONNECTED, BT_HEARING_AIDS_CONNECTED_HISTORY);
                HistoryType.TYPE_HEARING_DEVICES_CONNECTED, BT_HEARING_DEVICES_CONNECTED_HISTORY);
        HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
                HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, BT_HEARABLE_DEVICES_PAIRED_HISTORY);
        HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
                HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, BT_HEARABLE_DEVICES_CONNECTED_HISTORY);
    }

    private HearingAidStatsLogUtils() {}
}
+81 −13
Original line number Diff line number Diff line
@@ -21,8 +21,11 @@ import static com.android.settingslib.bluetooth.HearingAidStatsLogUtils.PAIRED_H

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothProfile;
import android.content.Context;

import androidx.test.core.app.ApplicationProvider;
@@ -42,6 +45,7 @@ import java.time.LocalDate;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@RunWith(RobolectricTestRunner.class)
@@ -49,7 +53,7 @@ public class HearingAidStatsLogUtilsTest {

    private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
    private static final int TEST_HISTORY_TYPE =
            HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED;
            HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED;

    @Rule
    public final MockitoRule mockito = MockitoJUnit.rule();
@@ -128,20 +132,20 @@ public class HearingAidStatsLogUtilsTest {
    }

    @Test
    public void getUserCategory_hearingAidsUser() {
        prepareHearingAidsUserHistory();
    public void getUserCategory_hearingDevicesUser() {
        prepareHearingDevicesUserHistory();

        assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
                HearingAidStatsLogUtils.CATEGORY_HEARING_AIDS);
                HearingAidStatsLogUtils.CATEGORY_HEARING_DEVICES);
    }

    @Test
    public void getUserCategory_newHearingAidsUser() {
        prepareHearingAidsUserHistory();
    public void getUserCategory_newHearingDevicesUser() {
        prepareHearingDevicesUserHistory();
        prepareNewUserHistory();

        assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
                HearingAidStatsLogUtils.CATEGORY_NEW_HEARING_AIDS);
                HearingAidStatsLogUtils.CATEGORY_NEW_HEARING_DEVICES);
    }

    @Test
@@ -162,12 +166,56 @@ public class HearingAidStatsLogUtilsTest {
    }

    @Test
    public void getUserCategory_bothHearingAidsAndHearableDevicesUser_returnHearingAidsUser() {
        prepareHearingAidsUserHistory();
    public void getUserCategory_bothHearingAndHearableDevicesUser_returnHearingDevicesUser() {
        prepareHearingDevicesUserHistory();
        prepareHearableDevicesUserHistory();

        assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
                HearingAidStatsLogUtils.CATEGORY_HEARING_AIDS);
                HearingAidStatsLogUtils.CATEGORY_HEARING_DEVICES);
    }

    @Test
    public void updateHistoryIfNeeded_ashaHearingDevice_ashaConnected_historyCorrect() {
        prepareAshaHearingDevice();

        HearingAidProfile ashaProfile = mock(HearingAidProfile.class);
        HearingAidStatsLogUtils.updateHistoryIfNeeded(mContext, mCachedBluetoothDevice, ashaProfile,
                BluetoothProfile.STATE_CONNECTED);

        assertHistorySize(HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED, 1);
    }

    @Test
    public void updateHistoryIfNeeded_leAudioHearingDevice_hapClientConnected_historyCorrect() {
        prepareLeAudioHearingDevice();

        HapClientProfile hapClientProfile = mock(HapClientProfile.class);
        HearingAidStatsLogUtils.updateHistoryIfNeeded(mContext, mCachedBluetoothDevice,
                hapClientProfile, BluetoothProfile.STATE_CONNECTED);

        assertHistorySize(HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED, 1);
    }

    @Test
    public void updateHistoryIfNeeded_leAudioHearingDevice_leAudioConnected_historyCorrect() {
        prepareLeAudioHearingDevice();

        LeAudioProfile leAudioProfile = mock(LeAudioProfile.class);
        HearingAidStatsLogUtils.updateHistoryIfNeeded(mContext, mCachedBluetoothDevice,
                leAudioProfile, BluetoothProfile.STATE_CONNECTED);

        assertHistorySize(HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, 0);
    }

    @Test
    public void updateHistoryIfNeeded_hearableDevice_leAudioConnected_historyCorrect() {
        prepareHearableDevice();

        LeAudioProfile leAudioProfile = mock(LeAudioProfile.class);
        HearingAidStatsLogUtils.updateHistoryIfNeeded(mContext, mCachedBluetoothDevice,
                leAudioProfile, BluetoothProfile.STATE_CONNECTED);

        assertHistorySize(HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, 1);
    }

    private long convertToStartOfDayTime(long timestamp) {
@@ -176,12 +224,12 @@ public class HearingAidStatsLogUtilsTest {
        return date.atStartOfDay(zoneId).toInstant().toEpochMilli();
    }

    private void prepareHearingAidsUserHistory() {
    private void prepareHearingDevicesUserHistory() {
        final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
        for (int i = CONNECTED_HISTORY_EXPIRED_DAY - 1; i >= 0; i--) {
            final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(i);
            HearingAidStatsLogUtils.addToHistory(mContext,
                    HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED, data);
                    HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED, data);
        }
    }

@@ -198,8 +246,28 @@ public class HearingAidStatsLogUtilsTest {
        final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
        final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(PAIRED_HISTORY_EXPIRED_DAY - 1);
        HearingAidStatsLogUtils.addToHistory(mContext,
                HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED, data);
                HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED, data);
        HearingAidStatsLogUtils.addToHistory(mContext,
                HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, data);
    }

    private void prepareAshaHearingDevice() {
        doReturn(List.of(mock(HearingAidProfile.class))).when(mCachedBluetoothDevice).getProfiles();
    }

    private void prepareLeAudioHearingDevice() {
        doReturn(List.of(mock(HapClientProfile.class), mock(LeAudioProfile.class))).when(
                mCachedBluetoothDevice).getProfiles();
    }

    private void prepareHearableDevice() {
        doReturn(List.of(mock(A2dpProfile.class), mock(HeadsetProfile.class),
                mock(LeAudioProfile.class))).when(mCachedBluetoothDevice).getProfiles();
    }

    private void assertHistorySize(int type, int size) {
        LinkedList<Long> history = HearingAidStatsLogUtils.getHistory(mContext, type);
        assertThat(history).isNotNull();
        assertThat(history.size()).isEqualTo(size);
    }
}