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

Commit b93d5f18 authored by Jason Hsu's avatar Jason Hsu Committed by Android (Google) Code Review
Browse files

Merge "[Audio Routing] Add audio routing functionality for hearing device"

parents 88c0769e 31ff5d92
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -30,7 +30,8 @@
        android:summary="%s"
        android:key="audio_routing_ringtone"
        android:persistent="false"
        android:title="@string/bluetooth_ringtone_title" />
        android:title="@string/bluetooth_ringtone_title"
        settings:controller="com.android.settings.bluetooth.HearingDeviceRingtoneRoutingPreferenceController" />

    <ListPreference
        android:entries="@array/bluetooth_audio_routing_titles"
@@ -38,7 +39,8 @@
        android:summary="%s"
        android:key="audio_routing_call"
        android:persistent="false"
        android:title="@string/bluetooth_call_title" />
        android:title="@string/bluetooth_call_title"
        settings:controller="com.android.settings.bluetooth.HearingDeviceCallRoutingPreferenceController" />

    <ListPreference
        android:entries="@array/bluetooth_audio_routing_titles"
@@ -46,7 +48,8 @@
        android:summary="%s"
        android:key="audio_routing_media"
        android:persistent="false"
        android:title="@string/bluetooth_media_title" />
        android:title="@string/bluetooth_media_title"
        settings:controller="com.android.settings.bluetooth.HearingDeviceMediaRoutingPreferenceController" />

    <ListPreference
        android:entries="@array/bluetooth_audio_routing_titles"
@@ -54,6 +57,7 @@
        android:summary="%s"
        android:key="audio_routing_system_sounds"
        android:persistent="false"
        android:title="@string/bluetooth_system_sounds_title" />
        android:title="@string/bluetooth_system_sounds_title"
        settings:controller="com.android.settings.bluetooth.HearingDeviceSystemSoundsRoutingPreferenceController" />

</PreferenceScreen>
+4 −1
Original line number Diff line number Diff line
@@ -65,7 +65,10 @@ public class BluetoothDetailsAudioRoutingFragment extends RestrictedDashboardFra
            return;
        }

        // TODO: mCachedDevice will pass to control in next CLs.
        use(HearingDeviceRingtoneRoutingPreferenceController.class).init(mCachedDevice);
        use(HearingDeviceCallRoutingPreferenceController.class).init(mCachedDevice);
        use(HearingDeviceMediaRoutingPreferenceController.class).init(mCachedDevice);
        use(HearingDeviceSystemSoundsRoutingPreferenceController.class).init(mCachedDevice);
    }

    @Override
+205 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.bluetooth;

import android.content.ContentResolver;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import android.util.Log;

import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import androidx.preference.ListPreference;
import androidx.preference.Preference;

import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;

import com.google.common.primitives.Ints;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Abstract class for providing audio routing {@link ListPreference} common control for hearing
 * device specifically.
 */
public abstract class HearingDeviceAudioRoutingBasePreferenceController extends
        BasePreferenceController implements Preference.OnPreferenceChangeListener {

    private static final String TAG = "HARoutingBasePreferenceController";

    private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
            AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");

    private final AudioManager mAudioManager;

    public HearingDeviceAudioRoutingBasePreferenceController(Context context,
            String preferenceKey) {
        super(context, preferenceKey);
        mAudioManager = mContext.getSystemService(AudioManager.class);
    }

    @Override
    public int getAvailabilityStatus() {
        return AVAILABLE;
    }

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

        final ListPreference listPreference = (ListPreference) preference;
        final int routingValue = restoreRoutingValue(mContext);
        listPreference.setValue(String.valueOf(routingValue));
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        final ListPreference listPreference = (ListPreference) preference;
        final Integer routingValue = Ints.tryParse((String) newValue);
        final AudioDeviceAttributes hearingDeviceAttribute = new AudioDeviceAttributes(
                AudioDeviceAttributes.ROLE_OUTPUT,
                AudioDeviceInfo.TYPE_HEARING_AID,
                getHearingDevice().getAddress());
        final List<AudioProductStrategy> supportedStrategies = getSupportedStrategies(
                getSupportedAttributeList());

        boolean status = false;
        if (routingValue != null) {
            switch (routingValue) {
                case RoutingValue.AUTO:
                    status = removePreferredDeviceForStrategies(supportedStrategies);
                    break;
                case RoutingValue.HEARING_DEVICE:
                    removePreferredDeviceForStrategies(supportedStrategies);
                    status = setPreferredDeviceForStrategies(supportedStrategies,
                            hearingDeviceAttribute);
                    break;
                case RoutingValue.DEVICE_SPEAKER:
                    removePreferredDeviceForStrategies(supportedStrategies);
                    status = setPreferredDeviceForStrategies(supportedStrategies,
                            DEVICE_SPEAKER_OUT);
                    break;
                default:
                    throw new IllegalArgumentException("Unexpected routingValue: " + routingValue);
            }
        }
        if (!status) {
            Log.w(TAG, "routingMode: " + listPreference.getKey() + "routingValue: " + routingValue
                    + " fail to configure AudioProductStrategy");
        }

        saveRoutingValue(mContext, routingValue);
        updateState(listPreference);
        return true;
    }

    /**
     * Gets a list of usage value defined in {@link AudioAttributes} that is used to configure
     * audio routing via {@link AudioProductStrategy}.
     */
    protected abstract int[] getSupportedAttributeList();

    /**
     * Gets the {@link CachedBluetoothDevice} hearing device that is used to configure audio
     * routing.
     */
    protected abstract CachedBluetoothDevice getHearingDevice();

    /**
     * Saves the {@link RoutingValue}.
     *
     * @param context the valid context used to get the {@link ContentResolver}
     * @param routingValue the value defined in {@link RoutingValue}
     */
    protected abstract void saveRoutingValue(Context context, int routingValue);

    /**
     * Restores the {@link RoutingValue} and used to reflect status on ListPreference.
     *
     * @param context the valid context used to get the {@link ContentResolver}
     * @return one of {@link RoutingValue}
     */
    protected abstract int restoreRoutingValue(Context context);

    private List<AudioProductStrategy> getSupportedStrategies(int[] attributeSdkUsageList) {
        final List<AudioAttributes> audioAttrList = new ArrayList<>(attributeSdkUsageList.length);
        for (int attributeSdkUsage : attributeSdkUsageList) {
            audioAttrList.add(new AudioAttributes.Builder().setUsage(attributeSdkUsage).build());
        }

        final List<AudioProductStrategy> allStrategies = getAudioProductStrategies();
        final List<AudioProductStrategy> supportedStrategies = new ArrayList<>();
        for (AudioProductStrategy strategy : allStrategies) {
            for (AudioAttributes audioAttr : audioAttrList) {
                if (strategy.supportsAudioAttributes(audioAttr)) {
                    supportedStrategies.add(strategy);
                }
            }
        }

        return supportedStrategies.stream().distinct().collect(Collectors.toList());
    }

    @VisibleForTesting
    List<AudioProductStrategy> getAudioProductStrategies() {
        return AudioManager.getAudioProductStrategies();
    }

    @VisibleForTesting
    boolean setPreferredDeviceForStrategies(List<AudioProductStrategy> strategies,
            AudioDeviceAttributes audioDevice) {
        boolean status = true;
        for (AudioProductStrategy strategy : strategies) {
            status &= mAudioManager.setPreferredDeviceForStrategy(strategy, audioDevice);
        }

        return status;
    }

    @VisibleForTesting
    boolean removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies) {
        boolean status = true;
        for (AudioProductStrategy strategy : strategies) {
            status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
        }

        return status;
    }

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            RoutingValue.AUTO,
            RoutingValue.HEARING_DEVICE,
            RoutingValue.DEVICE_SPEAKER,
    })

    @VisibleForTesting
    protected @interface RoutingValue {
        int AUTO = 0;
        int HEARING_DEVICE = 1;
        int DEVICE_SPEAKER = 2;
    }
}
+69 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.bluetooth;

import android.content.Context;
import android.media.AudioAttributes;
import android.provider.Settings;

import com.android.settingslib.bluetooth.CachedBluetoothDevice;

/**
 * The controller of the hearing device call routing list preference.
 */
public class HearingDeviceCallRoutingPreferenceController extends
        HearingDeviceAudioRoutingBasePreferenceController {

    private CachedBluetoothDevice mHearingDevice;

    public HearingDeviceCallRoutingPreferenceController(Context context, String preferenceKey) {
        super(context, preferenceKey);
    }

    /**
     * Initializes objects in this controller. Need to call this before using the controller.
     *
     * @param cachedBluetoothDevice the hearing device to configure audio routing
     */
    public void init(CachedBluetoothDevice cachedBluetoothDevice) {
        mHearingDevice = cachedBluetoothDevice;
    }

    @Override
    protected int[] getSupportedAttributeList() {
        return new int[]{
                AudioAttributes.USAGE_VOICE_COMMUNICATION,
                AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING};
    }

    @Override
    protected CachedBluetoothDevice getHearingDevice() {
        return mHearingDevice;
    }

    @Override
    protected void saveRoutingValue(Context context, int routingValue) {
        Settings.Secure.putInt(context.getContentResolver(),
                Settings.Secure.HEARING_AID_CALL_ROUTING, routingValue);
    }

    @Override
    protected int restoreRoutingValue(Context context) {
        return Settings.Secure.getInt(context.getContentResolver(),
                Settings.Secure.HEARING_AID_CALL_ROUTING, RoutingValue.AUTO);
    }
}
+69 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.bluetooth;

import android.content.Context;
import android.media.AudioAttributes;
import android.provider.Settings;

import com.android.settingslib.bluetooth.CachedBluetoothDevice;

/**
 * The controller of the hearing device media routing list preference.
 */
public class HearingDeviceMediaRoutingPreferenceController extends
        HearingDeviceAudioRoutingBasePreferenceController {

    private CachedBluetoothDevice mHearingDevice;

    public HearingDeviceMediaRoutingPreferenceController(Context context, String preferenceKey) {
        super(context, preferenceKey);
    }

    /**
     * Initializes objects in this controller. Need to call this before using the controller.
     *
     * @param cachedBluetoothDevice the hearing device to configure audio routing
     */
    public void init(CachedBluetoothDevice cachedBluetoothDevice) {
        mHearingDevice = cachedBluetoothDevice;
    }

    @Override
    protected int[] getSupportedAttributeList() {
        return new int[]{
                AudioAttributes.USAGE_MEDIA,
                AudioAttributes.USAGE_GAME};
    }

    @Override
    protected CachedBluetoothDevice getHearingDevice() {
        return mHearingDevice;
    }

    @Override
    protected void saveRoutingValue(Context context, int routingValue) {
        Settings.Secure.putInt(context.getContentResolver(),
                Settings.Secure.HEARING_AID_MEDIA_ROUTING, routingValue);
    }

    @Override
    protected int restoreRoutingValue(Context context) {
        return Settings.Secure.getInt(context.getContentResolver(),
                Settings.Secure.HEARING_AID_MEDIA_ROUTING, RoutingValue.AUTO);
    }
}
Loading