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

Commit cef4313e authored by tim peng's avatar tim peng Committed by Android (Google) Code Review
Browse files

Merge "Add base dialog preference controller for new Bluetooth developer option"

parents 1082bca0 78b0edfe
Loading
Loading
Loading
Loading
+235 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.development.bluetooth;

import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.util.Log;

import androidx.preference.Preference;

import com.android.settings.development.BluetoothA2dpConfigStore;
import com.android.settingslib.core.lifecycle.Lifecycle;

/**
 * Abstract class for Bluetooth A2DP config dialog controller in developer option.
 */
public abstract class AbstractBluetoothDialogPreferenceController extends
        AbstractBluetoothPreferenceController implements BaseBluetoothDialogPreference.Callback {

    private static final String TAG = "AbstractBtDlgCtr";

    protected static final int[] CODEC_TYPES = {BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
            BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
            BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC};
    protected static final int[] SAMPLE_RATES = {BluetoothCodecConfig.SAMPLE_RATE_192000,
            BluetoothCodecConfig.SAMPLE_RATE_176400,
            BluetoothCodecConfig.SAMPLE_RATE_96000,
            BluetoothCodecConfig.SAMPLE_RATE_88200,
            BluetoothCodecConfig.SAMPLE_RATE_48000,
            BluetoothCodecConfig.SAMPLE_RATE_44100};
    protected static final int[] BITS_PER_SAMPLES = {BluetoothCodecConfig.BITS_PER_SAMPLE_32,
            BluetoothCodecConfig.BITS_PER_SAMPLE_24,
            BluetoothCodecConfig.BITS_PER_SAMPLE_16};
    protected static final int[] CHANNEL_MODES = {BluetoothCodecConfig.CHANNEL_MODE_STEREO,
            BluetoothCodecConfig.CHANNEL_MODE_MONO};

    public AbstractBluetoothDialogPreferenceController(Context context, Lifecycle lifecycle,
                                                       BluetoothA2dpConfigStore store) {
        super(context, lifecycle, store);
    }

    @Override
    public void updateState(Preference preference) {
        super.updateState(preference);
    }

    @Override
    public CharSequence getSummary() {
        return ((BaseBluetoothDialogPreference) mPreference).generateSummary(
                getCurrentConfigIndex());
    }

    @Override
    public void onIndexUpdated(int index) {
        final BluetoothA2dp bluetoothA2dp = mBluetoothA2dp;
        if (bluetoothA2dp == null) {
            return;
        }
        writeConfigurationValues(index);
        final BluetoothCodecConfig codecConfig = mBluetoothA2dpConfigStore.createCodecConfig();
        bluetoothA2dp.setCodecConfigPreference(null, codecConfig);
        mPreference.setSummary(((BaseBluetoothDialogPreference) mPreference).generateSummary(
                index));
    }

    @Override
    public int getCurrentConfigIndex() {
        final BluetoothCodecConfig codecConfig = getCurrentCodecConfig();
        if (codecConfig == null) {
            Log.d(TAG, "Unable to get current config index. Current codec Config is null.");
            return getDefaultIndex();
        }
        return getCurrentIndexByConfig(codecConfig);
    }

    /**
     * Updates the new value to the {@link BluetoothA2dpConfigStore}.
     *
     * @param newValue the new setting value
     */
    protected abstract void writeConfigurationValues(int newValue);

    /**
     * To get the current A2DP index value.
     *
     * @param config for the current {@link BluetoothCodecConfig}.
     * @return the current index.
     */
    protected abstract int getCurrentIndexByConfig(BluetoothCodecConfig config);

    /**
     * @return the default index.
     */
    protected int getDefaultIndex() {
        return ((BaseBluetoothDialogPreference) mPreference).getDefaultIndex();
    }

    /**
     * To get the current A2DP codec config.
     *
     * @return {@link BluetoothCodecConfig}.
     */
    protected BluetoothCodecConfig getCurrentCodecConfig() {
        final BluetoothA2dp bluetoothA2dp = mBluetoothA2dp;
        if (bluetoothA2dp == null) {
            return null;
        }
        final BluetoothCodecStatus codecStatus = bluetoothA2dp.getCodecStatus(null);
        if (codecStatus == null) {
            Log.d(TAG, "Unable to get current codec config. Codec status is null");
            return null;
        }
        return codecStatus.getCodecConfig();
    }

    /**
     * To get the selectable A2DP configs.
     *
     * @return Array of {@link BluetoothCodecConfig}.
     */
    protected BluetoothCodecConfig[] getSelectableConfigs(BluetoothDevice device) {
        final BluetoothA2dp bluetoothA2dp = mBluetoothA2dp;
        if (bluetoothA2dp == null) {
            return null;
        }
        final BluetoothCodecStatus codecStatus = bluetoothA2dp.getCodecStatus(device);
        if (codecStatus != null) {
            return codecStatus.getCodecsSelectableCapabilities();
        }
        return null;
    }

    /**
     * To get the selectable A2DP config by codec type.
     *
     * @return {@link BluetoothCodecConfig}.
     */
    protected BluetoothCodecConfig getSelectableByCodecType(int codecTypeValue) {
        final BluetoothCodecConfig[] configs = getSelectableConfigs(null);
        if (configs == null) {
            Log.d(TAG, "Unable to get selectable config. Selectable configs is empty.");
            return null;
        }
        for (BluetoothCodecConfig config : configs) {
            if (config.getCodecType() == codecTypeValue) {
                return config;
            }
        }
        Log.d(TAG, "Unable to find matching codec config, type is " + codecTypeValue);
        return null;
    }

    /**
     * Method to notify controller when the HD audio(optional codec) state is changed.
     *
     * @param enabled Is {@code true} when the setting is enabled.
     */
    public void onHDAudioEnabled(boolean enabled) {};

    static int getHighestCodec(BluetoothCodecConfig[] configs) {
        if (configs == null) {
            Log.d(TAG, "Unable to get highest codec. Configs are empty");
            return BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID;
        }
        for (int i = 0; i < CODEC_TYPES.length; i++) {
            for (int j = 0; j < configs.length; j++) {
                if ((configs[j].getCodecType() == CODEC_TYPES[i])) {
                    return CODEC_TYPES[i];
                }
            }
        }
        return BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID;
    }

    static int getHighestSampleRate(BluetoothCodecConfig config) {
        if (config == null) {
            Log.d(TAG, "Unable to get highest sample rate. Config is empty");
            return BluetoothCodecConfig.SAMPLE_RATE_NONE;
        }
        final int capability = config.getSampleRate();
        for (int i = 0; i < SAMPLE_RATES.length; i++) {
            if ((capability & SAMPLE_RATES[i]) != 0) {
                return SAMPLE_RATES[i];
            }
        }
        return BluetoothCodecConfig.SAMPLE_RATE_NONE;
    }

    static int getHighestBitsPerSample(BluetoothCodecConfig config) {
        if (config == null) {
            Log.d(TAG, "Unable to get highest bits per sample. Config is empty");
            return BluetoothCodecConfig.BITS_PER_SAMPLE_NONE;
        }
        final int capability = config.getBitsPerSample();
        for (int i = 0; i < BITS_PER_SAMPLES.length; i++) {
            if ((capability & BITS_PER_SAMPLES[i]) != 0) {
                return BITS_PER_SAMPLES[i];
            }
        }
        return BluetoothCodecConfig.BITS_PER_SAMPLE_NONE;
    }

    static int getHighestChannelMode(BluetoothCodecConfig config) {
        if (config == null) {
            Log.d(TAG, "Unable to get highest channel mode. Config is empty");
            return BluetoothCodecConfig.CHANNEL_MODE_NONE;
        }
        final int capability = config.getChannelMode();
        for (int i = 0; i < CHANNEL_MODES.length; i++) {
            if ((capability & CHANNEL_MODES[i]) != 0) {
                return CHANNEL_MODES[i];
            }
        }
        return BluetoothCodecConfig.CHANNEL_MODE_NONE;
    }
}
+217 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.development.bluetooth;

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

import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.content.Context;

import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceScreen;

import com.android.settings.development.BluetoothA2dpConfigStore;
import com.android.settingslib.core.lifecycle.Lifecycle;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

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

@RunWith(RobolectricTestRunner.class)
public class AbstractBluetoothDialogPreferenceControllerTest {

    private static final String SUMMARY = "Test summary";

    @Mock
    private BluetoothA2dp mBluetoothA2dp;
    @Mock
    private PreferenceScreen mScreen;

    private AbstractBluetoothDialogPreferenceController mController;
    private BaseBluetoothDialogPreferenceImpl mPreference;
    private BluetoothA2dpConfigStore mBluetoothA2dpConfigStore;
    private BluetoothCodecStatus mCodecStatus;
    private BluetoothCodecConfig mCodecConfigAAC;
    private BluetoothCodecConfig mCodecConfigSBC;
    private BluetoothCodecConfig[] mCodecConfigs = new BluetoothCodecConfig[2];
    private Context mContext;
    private int mCurrentConfig;
    private LifecycleOwner mLifecycleOwner;
    private Lifecycle mLifecycle;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mContext = RuntimeEnvironment.application;
        mLifecycleOwner = () -> mLifecycle;
        mLifecycle = new Lifecycle(mLifecycleOwner);
        mBluetoothA2dpConfigStore = spy(new BluetoothA2dpConfigStore());
        mController = spy(new AbstractBluetoothDialogPreferenceControllerImpl(mContext, mLifecycle,
                mBluetoothA2dpConfigStore));
        mPreference = spy(new BaseBluetoothDialogPreferenceImpl(mContext));

        mCodecConfigAAC = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC);
        mCodecConfigSBC = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC);
        mCodecConfigs[0] = mCodecConfigAAC;
        mCodecConfigs[1] = mCodecConfigSBC;

        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
        mController.displayPreference(mScreen);
        mCurrentConfig = mController.getCurrentConfigIndex();
        when(mPreference.generateSummary(mCurrentConfig)).thenReturn(SUMMARY);
    }

    @Test
    public void getSummary_generateSummary() {
        assertThat(mController.getSummary()).isEqualTo(SUMMARY);
    }
    @Test
    public void onIndexUpdated_A2dpNotReady() {
        mController.onIndexUpdated(mController.getCurrentConfigIndex());

        verify(mController, never()).writeConfigurationValues(mCurrentConfig);
    }

    @Test
    public void onIndexUpdated_checkFlow() {
        when(mBluetoothA2dpConfigStore.createCodecConfig()).thenReturn(mCodecConfigAAC);
        mController.onBluetoothServiceConnected(mBluetoothA2dp);
        mController.onIndexUpdated(mCurrentConfig);

        verify(mController).writeConfigurationValues(mCurrentConfig);
        verify(mBluetoothA2dp).setCodecConfigPreference(null, mCodecConfigAAC);
        assertThat(mPreference.getSummary()).isEqualTo(SUMMARY);
    }

    @Test
    public void getCurrentConfigIndex_noCodecConfig_returnDefaultIndex() {
        when(mController.getCurrentCodecConfig()).thenReturn(null);

        assertThat(mController.getCurrentConfigIndex()).isEqualTo(mPreference.getDefaultIndex());
    }

    @Test
    public void getCurrentConfigIndex_returnCurrentIndex() {
        when(mController.getCurrentCodecConfig()).thenReturn(mCodecConfigAAC);
        mController.getCurrentConfigIndex();

        verify(mController).getCurrentIndexByConfig(mCodecConfigAAC);
    }

    @Test
    public void getCurrentCodecConfig_errorChecking() {
        mController.onBluetoothServiceConnected(null);
        assertThat(mController.getCurrentCodecConfig()).isNull();

        mController.onBluetoothServiceConnected(mBluetoothA2dp);
        when(mBluetoothA2dp.getCodecStatus(null)).thenReturn(null);
        assertThat(mController.getCurrentCodecConfig()).isNull();
    }

    @Test
    public void getCurrentCodecConfig_verifyConfig() {
        mController.onBluetoothServiceConnected(mBluetoothA2dp);
        mCodecStatus = new BluetoothCodecStatus(mCodecConfigAAC, null, null);
        when(mBluetoothA2dp.getCodecStatus(null)).thenReturn(mCodecStatus);

        assertThat(mController.getCurrentCodecConfig()).isEqualTo(mCodecConfigAAC);
    }

    @Test
    public void getSelectableConfigs_verifyConfig() {
        mController.onBluetoothServiceConnected(mBluetoothA2dp);
        mCodecStatus = new BluetoothCodecStatus(mCodecConfigAAC, null, mCodecConfigs);
        when(mBluetoothA2dp.getCodecStatus(null)).thenReturn(mCodecStatus);

        assertThat(mController.getSelectableConfigs(null)).isEqualTo(mCodecConfigs);
    }

    @Test
    public void getSelectableByCodecType_verifyConfig() {
        mController.onBluetoothServiceConnected(mBluetoothA2dp);
        mCodecStatus = new BluetoothCodecStatus(mCodecConfigAAC, null, mCodecConfigs);
        when(mBluetoothA2dp.getCodecStatus(null)).thenReturn(mCodecStatus);

        assertThat(mController.getSelectableByCodecType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC))
                .isEqualTo(mCodecConfigAAC);
    }

    @Test
    public void getSelectableByCodecType_unavailable() {
        mController.onBluetoothServiceConnected(mBluetoothA2dp);
        mCodecStatus = new BluetoothCodecStatus(mCodecConfigAAC, null, mCodecConfigs);
        when(mBluetoothA2dp.getCodecStatus(null)).thenReturn(mCodecStatus);

        assertThat(mController.getSelectableByCodecType(
                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX)).isNull();
    }

    private static class AbstractBluetoothDialogPreferenceControllerImpl extends
            AbstractBluetoothDialogPreferenceController {

        private AbstractBluetoothDialogPreferenceControllerImpl(Context context,
                Lifecycle lifecycle, BluetoothA2dpConfigStore store) {
            super(context, lifecycle, store);
        }

        @Override
        public String getPreferenceKey() {
            return "KEY";
        }

        @Override
        protected void writeConfigurationValues(int newValue) {
        }

        @Override
        protected int getCurrentIndexByConfig(BluetoothCodecConfig config) {
            return 0;
        }

        @Override
        public List<Integer> getSelectableIndex() {
            return new ArrayList<>();
        }
    }

    private static class BaseBluetoothDialogPreferenceImpl extends BaseBluetoothDialogPreference {

        private BaseBluetoothDialogPreferenceImpl(Context context) {
            super(context);
            mSummaryStrings.add(SUMMARY);
        }

        @Override
        protected int getRadioButtonGroupId() {
            return 0;
        }
    }
}