Proceso de ajuste de volumen de Android Automotive

El sistema operativo Android Automotive (AAOS) está construido sobre el sistema principal de Android para admitir el uso de Android como sistema de información y entretenimiento para vehículos. El sistema de audio AAOS ha ampliado el sistema de audio central de Android, ha creado nuevos conceptos y abstracciones, como zonas de sonido, ha proporcionado nuevas interfaces API y ha añadido nuevos servicios para satisfacer las necesidades especiales de las cabinas inteligentes. La documentación oficial de Google Android Automotive and Car Audio proporciona una descripción clara y autorizada de la arquitectura del sistema AAOS y la arquitectura del subsistema de audio AAOS.

Comencemos con la parte de ajuste de volumen en la configuración de la aplicación del sistema AAOS y veamos la implementación de la operación de ajuste de volumen en AAOS. Es decir, la siguiente parte de la interfaz de usuario de la aplicación del sistema Configuración:

Configuración de sonido automotriz de Android

El análisis de código a continuación se basa en la versión android-12.1.0_r27.

Ajustar el volumen en la aplicación.

AAOS implementa una aplicación del sistema de Configuración que es diferente de la versión móvil y de la versión de tableta, y su código se encuentra en paquetes/aplicaciones/Car/Configuración . La entrada del código para la lógica relacionada con la configuración de sonido es VolumeSettingsPreferenceController( packages/apps/Car/Settings/src/com/android/car/settings/sound/VolumeSettingsPreferenceController.java ). Cuando el usuario hace clic en el elemento Sonido a la izquierda , VolumeSettingsPreferenceControllerse crea el objeto. Durante el proceso de creación del objeto, se hacen las siguientes cosas:

  1. Carobtenido de CarAudioManager;
  2. Cargue todos los elementos de volumen descritos en el archivo de configuración, cada elemento de volumen describe la información de un grupo de volúmenes;
  3. CarAudioManagerObtenga el número de grupos de volumen en el área de sonido principal de
  4. Para cada CarAudioManagerobtenga la información de uso del grupo de volumen desdegrupo de volumen en la zona de sonido,VolumeSeekBarPreference
  5. Registre CarAudioManagerla devolución de llamada de cambio de volumen y supervise los eventos de cambio de volumen del sistema. Después de que la aplicación del sistema de configuración u otras aplicaciones ajustan el volumen, se llama aquí a la devolución de llamada para finalmente actualizar la interfaz de usuario.

El código específico para el proceso descrito anteriormente es el siguiente:

    public VolumeSettingsPreferenceController(Context context, String preferenceKey,
            FragmentController fragmentController,
            CarUxRestrictions uxRestrictions) {
        this(context, preferenceKey, fragmentController, uxRestrictions, Car.createCar(context),
                new VolumeSettingsRingtoneManager(context));
    }

    @VisibleForTesting
    VolumeSettingsPreferenceController(Context context, String preferenceKey,
            FragmentController fragmentController,
            CarUxRestrictions uxRestrictions, Car car,
            VolumeSettingsRingtoneManager ringtoneManager) {
        super(context, preferenceKey, fragmentController, uxRestrictions);
        mCar = car;
        mRingtoneManager = ringtoneManager;
        mVolumeItems = VolumeItemParser.loadAudioUsageItems(context, carVolumeItemsXml());
        mUiHandler = new Handler(Looper.getMainLooper());

        mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
        if (mCarAudioManager != null) {
            int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
            cleanUpVolumePreferences();
            // Populates volume slider items from volume groups to UI.
            for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
                VolumeItem volumeItem = getVolumeItemForUsages(
                        mCarAudioManager.getUsagesForVolumeGroupId(groupId));
                VolumeSeekBarPreference volumePreference = createVolumeSeekBarPreference(
                        groupId, volumeItem.getUsage(), volumeItem.getIcon(),
                        volumeItem.getMuteIcon(), volumeItem.getTitle());
                setClickableWhileDisabled(volumePreference, /* clickable= */ true, p -> {
                    if (hasUserRestrictionByDpm(getContext(), DISALLOW_ADJUST_VOLUME)) {
                        showActionDisabledByAdminDialog();
                    } else {
                        Toast.makeText(getContext(),
                                getContext().getString(R.string.action_unavailable),
                                Toast.LENGTH_LONG).show();
                    }
                });
                mVolumePreferences.add(volumePreference);
            }
            mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
        }
    }

En el paso 2 anterior, el archivo de configuración que mencionamos es car_volume_items.xml :

    @XmlRes
    @VisibleForTesting
    int carVolumeItemsXml() {
        return R.xml.car_volume_items;
    }

El archivo de configuración car_volume_items.xml se encuentra en paquetes/apps/Car/Settings/res/xml/car_volume_items.xml y su contenido es:

<?xml version="1.0" encoding="utf-8"?>
<!--
    Copyright 2018 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.
-->

<!--
  Defines all possible items on car volume settings UI, keyed by usage.

  This enables the CarSettings UI to associate VolumeGroups surfaced by
  CarAudioManager.getVolumeGroupCount with renderable assets (ie: title, icon)
  for presentation.

  Order matters in this configuration. If one volume group contains multiple
  audio usages, the first one appears in this file would be picked to be
  presented on UI.

  When overriding this configuration, please consult also the
  car_volume_groups.xml, which is read by car audio service.
-->
<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
    <item
        car:icon="@drawable/car_ic_phone_volume"
        car:mute_icon="@drawable/car_ic_phone_volume_mute"
        car:titleText="@*android:string/volume_call"
        car:usage="voice_communication"/>
    <item
        car:icon="@drawable/car_ic_phone_volume"
        car:mute_icon="@drawable/car_ic_phone_volume_mute"
        car:titleText="@*android:string/volume_call"
        car:usage="voice_communication_signalling"/>
    <item
        car:icon="@*android:drawable/ic_audio_media"
        car:mute_icon="@*android:drawable/ic_audio_media_mute"
        car:titleText="@*android:string/volume_music"
        car:usage="media"/>
    <item
        car:icon="@*android:drawable/ic_audio_media"
        car:mute_icon="@*android:drawable/ic_audio_media_mute"
        car:titleText="@*android:string/volume_music"
        car:usage="game"/>
    <item
        car:icon="@*android:drawable/ic_audio_alarm"
        car:mute_icon="@*android:drawable/ic_audio_alarm_mute"
        car:titleText="@*android:string/volume_alarm"
        car:usage="alarm"/>
    <item
        car:icon="@drawable/car_ic_navigation"
        car:mute_icon="@drawable/car_ic_navigation_mute"
        car:titleText="@string/navi_volume_title"
        car:usage="assistance_navigation_guidance"/>
    <item
        car:icon="@*android:drawable/ic_audio_ring_notif"
        car:mute_icon="@*android:drawable/ic_audio_ring_notif_mute"
        car:titleText="@*android:string/volume_ringtone"
        car:usage="notification_ringtone"/>
    <item
        car:icon="@*android:drawable/ic_audio_vol"
        car:mute_icon="@*android:drawable/ic_audio_vol_mute"
        car:titleText="@*android:string/volume_unknown"
        car:usage="assistant"/>
    <item
        car:icon="@*android:drawable/ic_audio_ring_notif"
        car:mute_icon="@*android:drawable/ic_audio_ring_notif_mute"
        car:titleText="@*android:string/volume_notification"
        car:usage="notification"/>
    <item
        car:icon="@*android:drawable/ic_audio_ring_notif"
        car:mute_icon="@*android:drawable/ic_audio_ring_notif_mute"
        car:titleText="@*android:string/volume_notification"
        car:usage="notification_communication_request"/>
    <item
        car:icon="@*android:drawable/ic_audio_ring_notif"
        car:mute_icon="@*android:drawable/ic_audio_ring_notif_mute"
        car:titleText="@*android:string/volume_notification"
        car:usage="notification_communication_instant"/>
    <item
        car:icon="@*android:drawable/ic_audio_ring_notif"
        car:mute_icon="@*android:drawable/ic_audio_ring_notif_mute"
        car:titleText="@*android:string/volume_notification"
        car:usage="notification_communication_delayed"/>
    <item
        car:icon="@*android:drawable/ic_audio_ring_notif"
        car:mute_icon="@*android:drawable/ic_audio_ring_notif_mute"
        car:titleText="@*android:string/volume_notification"
        car:usage="notification_event"/>
    <item
        car:icon="@*android:drawable/ic_audio_ring_notif"
        car:mute_icon="@*android:drawable/ic_audio_ring_notif_mute"
        car:titleText="@*android:string/volume_notification"
        car:usage="assistance_accessibility"/>
    <item
        car:icon="@*android:drawable/ic_audio_vol"
        car:mute_icon="@*android:drawable/ic_audio_vol_mute"
        car:titleText="@*android:string/volume_unknown"
        car:usage="assistance_sonification"/>
    <item
        car:icon="@*android:drawable/ic_audio_vol"
        car:mute_icon="@*android:drawable/ic_audio_vol_mute"
        car:titleText="@*android:string/volume_unknown"
        car:usage="unknown"/>
    <item
        car:icon="@*android:drawable/ic_audio_vol"
        car:mute_icon="@*android:drawable/ic_audio_vol_mute"
        car:titleText="@*android:string/volume_unknown"
        car:usage="call_assistant"/>
    <item
        car:icon="@*android:drawable/ic_audio_vol"
        car:mute_icon="@*android:drawable/ic_audio_vol_mute"
        car:titleText="@*android:string/volume_unknown"
        car:usage="emergency"/>
    <item
        car:icon="@*android:drawable/ic_audio_vol"
        car:mute_icon="@*android:drawable/ic_audio_vol_mute"
        car:titleText="@*android:string/volume_unknown"
        car:usage="safety"/>
    <item
        car:icon="@*android:drawable/ic_audio_vol"
        car:mute_icon="@*android:drawable/ic_audio_vol_mute"
        car:titleText="@*android:string/volume_unknown"
        car:usage="vehicle_status"/>
    <item
        car:icon="@*android:drawable/ic_audio_vol"
        car:mute_icon="@*android:drawable/ic_audio_vol_mute"
        car:titleText="@*android:string/volume_unknown"
        car:usage="announcement"/>
</carVolumeItems>

Los elementos en el archivo car_volume_items.xml son para un uso específico y no contienen ninguna información sobre la zona de sonido y el grupo de volumen. El grupo de volumen está asociado con el elemento de volumen a través de su uso, donde el uso está alineado con AudioAttributes.USAGE_*. VolumeItemParserAnalice el archivo de recursos xml y cree VolumeItemParser.VolumeItemuna lista:

package com.android.car.settings.sound;

import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.media.AudioAttributes;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.Xml;

import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import androidx.annotation.XmlRes;

import com.android.car.settings.R;
import com.android.car.settings.common.Logger;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;

/**
 * Parses the xml file which specifies which Audio usages should be considered by sound settings.
 */
public class VolumeItemParser {
    private static final Logger LOG = new Logger(VolumeItemParser.class);

    private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
    private static final String XML_TAG_VOLUME_ITEM = "item";

    /**
     * Parses the volume items listed in the xml resource provided. This is returned as a sparse
     * array which is keyed by the rank (the order in which the volume item appears in the xml
     * resrouce).
     */
    public static SparseArray<VolumeItem> loadAudioUsageItems(Context context,
            @XmlRes int volumeItemsXml) {
        SparseArray<VolumeItem> volumeItems = new SparseArray<>();
        try (XmlResourceParser parser = context.getResources().getXml(volumeItemsXml)) {
            AttributeSet attrs = Xml.asAttributeSet(parser);
            int type;
            // Traverse to the first start tag.
            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
                    && type != XmlResourceParser.START_TAG) {
                continue;
            }

            if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) {
                throw new RuntimeException("Meta-data does not start with carVolumeItems tag");
            }
            int outerDepth = parser.getDepth();
            int rank = 0;
            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
                    && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
                if (type == XmlResourceParser.END_TAG) {
                    continue;
                }
                if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
                    TypedArray item = context.getResources().obtainAttributes(
                            attrs, R.styleable.carVolumeItems_item);
                    int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
                    if (usage >= 0) {
                        volumeItems.put(usage, new VolumeItemParser.VolumeItem(
                                usage, rank,
                                item.getResourceId(R.styleable.carVolumeItems_item_titleText, 0),
                                item.getResourceId(R.styleable.carVolumeItems_item_icon, 0),
                                item.getResourceId(R.styleable.carVolumeItems_item_mute_icon, 0)));
                        rank++;
                    }
                    item.recycle();
                }
            }
        } catch (XmlPullParserException | IOException e) {
            LOG.e("Error parsing volume groups configuration", e);
        }
        return volumeItems;
    }

    /**
     * Wrapper class which contains information to render volume item on UI.
     */
    public static class VolumeItem {
        @AudioAttributes.AttributeUsage
        private final int mUsage;
        private final int mRank;
        @StringRes
        private final int mTitle;
        @DrawableRes
        private final int mIcon;
        @DrawableRes
        private final int mMuteIcon;

        /** Constructs the VolumeItem container with the given values. */
        public VolumeItem(@AudioAttributes.AttributeUsage int usage, int rank,
                @StringRes int title, @DrawableRes int icon, @DrawableRes int muteIcon) {
            mUsage = usage;
            mRank = rank;
            mTitle = title;
            mIcon = icon;
            mMuteIcon = muteIcon;
        }

        /**
         * Usage is used to represent what purpose the sound is used for. The values should be
         * defined within AudioAttributes.USAGE_*.
         */
        public int getUsage() {
            return mUsage;
        }

        /**
         * Rank represents the order in which the usage appears in
         * {@link R.xml#car_volume_items}. This order is used to determine which title and icon
         * should be used for each audio group. The lowest rank has the highest precedence.
         */
        public int getRank() {
            return mRank;
        }

        /** Title which should be used for the seek bar preference. */
        public int getTitle() {
            return mTitle;
        }

        /** Icon which should be used for the seek bar preference. */
        public int getIcon() {
            return mIcon;
        }

        /** Icon which should be used for the seek bar preference when muted. */
        public int getMuteIcon() {
            return mMuteIcon;
        }
    }
}

CarAudioManagerLa información de uso del grupo de volúmenes de muestra obtenida a través de es la siguiente:
Uso del grupo de volumen

El proceso de encontrar el elemento de volumen según el uso del grupo de volumen es encontrar el elemento de volumen mejor clasificado entre los usos de cada uso:

    private VolumeItem getVolumeItemForUsages(int[] usages) {
        int rank = Integer.MAX_VALUE;
        VolumeItem result = null;
        for (int usage : usages) {
            VolumeItem volumeItem = mVolumeItems.get(usage);
            if (volumeItem.getRank() < rank) {
                rank = volumeItem.getRank();
                result = volumeItem;
            }
        }
        return result;
    }

Más adelante aprenderemos sobre CarAudioManagerel proceso de obtener el número de grupos de volumen en la zona de sonido principal y obtener el uso de un grupo de volumen específico en una zona de sonido determinada.

Creado para un grupo de volúmenes VolumeSeekBarPreference, también define las acciones a realizar cuando el usuario ajusta el volumen:

    private VolumeSeekBarPreference createVolumeSeekBarPreference(
            int volumeGroupId, int usage, @DrawableRes int primaryIconResId,
            @DrawableRes int secondaryIconResId, @StringRes int titleId) {
        VolumeSeekBarPreference preference = new VolumeSeekBarPreference(getContext());
        preference.setTitle(getContext().getString(titleId));
        preference.setUnMutedIcon(getContext().getDrawable(primaryIconResId));
        preference.getUnMutedIcon().setTintList(
                getContext().getColorStateList(R.color.icon_color_default));
        preference.setMutedIcon(getContext().getDrawable(secondaryIconResId));
        preference.getMutedIcon().setTintList(
                getContext().getColorStateList(R.color.icon_color_default));
        try {
            preference.setValue(mCarAudioManager.getGroupVolume(volumeGroupId));
            preference.setMin(mCarAudioManager.getGroupMinVolume(volumeGroupId));
            preference.setMax(mCarAudioManager.getGroupMaxVolume(volumeGroupId));
            if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_MUTING)) {
                preference.setIsMuted(mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE,
                        volumeGroupId));
            }
        } catch (CarNotConnectedException e) {
            LOG.e("Car is not connected!", e);
        }
        preference.setContinuousUpdate(true);
        preference.setShowSeekBarValue(false);
        Bundle bundle = preference.getExtras();
        bundle.putInt(VOLUME_GROUP_KEY, volumeGroupId);
        bundle.putInt(VOLUME_USAGE_KEY, usage);
        preference.setOnPreferenceChangeListener((pref, newValue) -> {
            int prefGroup = pref.getExtras().getInt(VOLUME_GROUP_KEY);
            int prefUsage = pref.getExtras().getInt(VOLUME_USAGE_KEY);
            int newVolume = (Integer) newValue;
            setGroupVolume(prefGroup, newVolume);
            mRingtoneManager.playAudioFeedback(prefGroup, prefUsage);
            return true;
        });
        return preference;
    }

Al crear un grupo de volumen VolumeSeekBarPreference, el título, icono, etc. se configurará según el elemento de volumen, y el CarAudioManagervalor de volumen del grupo de volumen y su rango se obtendrán de. Desde VolumeSeekBarPreferencela devolución de llamada() configurada para setOnPreferenceChangeListener(), puede ver las acciones reales que se realizarán cuando el usuario ajuste el volumen. En concreto, establece principalmente CarAudioManagerel volumen de uso en el grupo de volumen de la zona de sonido y reproduce el tono de llamada:

    private void setGroupVolume(int volumeGroupId, int newVolume) {
        try {
            mCarAudioManager.setGroupVolume(volumeGroupId, newVolume, /* flags= */ 0);
        } catch (CarNotConnectedException e) {
            LOG.w("Ignoring volume change event because the car isn't connected", e);
        }
    }

Como puede ver aquí, la Configuración solo se puede usar para ajustar el volumen de cada grupo de volumen en el área de sonido principal. Cuando hay varias pantallas y varias zonas de sonido, es posible que sea necesario abrir la Configuración varias veces y ajustar el volumen de las diferentes zonas de sonido. En este momento, ¿es necesario algún mecanismo para pasar parámetros a Configuración para admitir el ajuste de volumen en múltiples zonas? ? ?

CaryCarAudioManager

Arriba vemos que la Configuración se Carobtiene de CarAudioManager, y posteriormente CarAudioManagerobtiene la zona de sonido y la información del grupo de volumen, y CarAudioManagerestablece el volumen de uso del grupo de volumen a través de . CarCreado por Car.createCar(context), es principalmente un proxy de cliente para el servicio "car_service". El servicio de carpeta "car_service" se ejecuta en com.android.carel proceso y su ciclo de vida lo gestionan los Servicecomponentes del mismo proceso CarService. Cuando CarServicese crea, el objeto de servicio de carpeta "car_service" se crea y se registra en el administrador de servicios, como por ejemplo ( paquetes/servicios/Car/service/src/com/android/car/CarService.java ):

    @Override
    public void onCreate() {
        LimitedTimingsTraceLog initTiming = new LimitedTimingsTraceLog(CAR_SERVICE_INIT_TIMING_TAG,
                Trace.TRACE_TAG_SYSTEM_SERVER, CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);
        initTiming.traceBegin("CarService.onCreate");

        initTiming.traceBegin("getVehicle");
        mVehicle = getVehicle();
        initTiming.traceEnd();

        EventLog.writeEvent(EventLogTags.CAR_SERVICE_CREATE, mVehicle == null ? 0 : 1);

        if (mVehicle == null) {
            throw new IllegalStateException("Vehicle HAL service is not available.");
        }
        try {
            mVehicleInterfaceName = mVehicle.interfaceDescriptor();
        } catch (RemoteException e) {
            throw new IllegalStateException("Unable to get Vehicle HAL interface descriptor", e);
        }

        Slog.i(CarLog.TAG_SERVICE, "Connected to " + mVehicleInterfaceName);
        EventLog.writeEvent(EventLogTags.CAR_SERVICE_CONNECTED, mVehicleInterfaceName);

        mICarImpl = new ICarImpl(this,
                mVehicle,
                SystemInterface.Builder.defaultSystemInterface(this).build(),
                mVehicleInterfaceName);
        mICarImpl.init();

        linkToDeath(mVehicle, mVehicleDeathRecipient);

        ServiceManager.addService("car_service", mICarImpl);
        SystemProperties.set("boot.car_service_created", "1");

        super.onCreate();

        initTiming.traceEnd(); // "CarService.onCreate"
    }

Car.createCar(context)Al crear Car:

  • Primero intente obtener el servicio "car_service" del ServiceManager y luego cree Carun objeto. Si el servicio "car_service" se obtiene con éxito, se llamará al componente car.startCarService()vinculante para evitar que el servicio "car_service" se elimine accidentalmente debido a las acciones de otros procesos o componentes y el final del ciclo de vida del componenteServiceCarServiceServiceCarService
  • Si la adquisición del servicio "car_service" falla, significa que es posible que ni Serviceel componente CarServiceni el servicio "car_service" se hayan creado aún. En este momento, llame al componente car.startCarService()vinculante e intente obtener el servicio "car_service" del ServiceManager nuevamente, y luego cree un proxy de cliente para el objeto de servicio "car_service" y proporcionelo a ;ServiceCarServiceCar
  • Es posible que se intente ejecutar varias veces la obtención del servicio "car_service" del ServiceManager.

La ejecución detallada del proceso anterior ( paquetes/servicios/Car/car-lib/src/android/car/Car.java ) es la siguiente:

    /**
     * Creates new {@link Car} object which connected synchronously to Car Service and ready to use.
     *
     * <p>Instance created with this should be disconnected from car service by calling
     * {@link #disconnect()} before the passed {code Context} is released.
     *
     * @param context application's context
     *
     * @return Car object if operation succeeded, otherwise null.
     */
    @Nullable
    public static Car createCar(Context context) {
        return createCar(context, (Handler) null);
    }

    /**
     * Creates new {@link Car} object which connected synchronously to Car Service and ready to use.
     *
     * <p>Instance created with this should be disconnected from car service by calling
     * {@link #disconnect()} before the passed {code Context} is released.
     *
     * @param context App's Context. This should not be null. If you are passing
     *                {@link ContextWrapper}, make sure that its base Context is non-null as well.
     *                Otherwise it will throw {@link java.lang.NullPointerException}.
     * @param handler the handler on which the manager's callbacks will be executed, or null to
     * execute on the application's main thread.
     *
     * @return Car object if operation succeeded, otherwise null.
     */
    @Nullable
    public static Car createCar(Context context, @Nullable Handler handler) {
        assertNonNullContext(context);
        Car car = null;
        IBinder service = null;
        boolean started = false;
        int retryCount = 0;
        while (true) {
            service = ServiceManager.getService(CAR_SERVICE_BINDER_SERVICE_NAME);
            if (car == null) {
                // service can be still null. The constructor is safe for null service.
                car = new Car(context, ICar.Stub.asInterface(service),
                        null /*serviceConnectionListener*/, null /*statusChangeListener*/, handler);
            }
            if (service != null) {
                if (!started) {  // specialization for most common case.
                    // Do this to crash client when car service crashes.
                    car.startCarService();
                    return car;
                }
                break;
            }
            if (!started) {
                car.startCarService();
                started = true;
            }
            retryCount++;
            if (retryCount > CAR_SERVICE_BINDER_POLLING_MAX_RETRY) {
                Log.e(TAG_CAR, "cannot get car_service, waited for car service (ms):"
                                + CAR_SERVICE_BINDER_POLLING_INTERVAL_MS
                                * CAR_SERVICE_BINDER_POLLING_MAX_RETRY,
                        new RuntimeException());
                return null;
            }
            try {
                Thread.sleep(CAR_SERVICE_BINDER_POLLING_INTERVAL_MS);
            } catch (InterruptedException e) {
                Log.e(CarLibLog.TAG_CAR, "interrupted while waiting for car_service",
                        new RuntimeException());
                return null;
            }
        }
        // Can be accessed from mServiceConnectionListener in main thread.
        synchronized (car) {
            if (car.mService == null) {
                car.mService = ICar.Stub.asInterface(service);
                Log.w(TAG_CAR,
                        "waited for car_service (ms):"
                                + CAR_SERVICE_BINDER_POLLING_INTERVAL_MS * retryCount,
                        new RuntimeException());
            }
            car.mConnectionState = STATE_CONNECTED;
        }
        return car;
    }
. . . . . .
    private Car(Context context, @Nullable ICar service,
            @Nullable ServiceConnection serviceConnectionListener,
            @Nullable CarServiceLifecycleListener statusChangeListener,
            @Nullable Handler handler) {
        mContext = context;
        mEventHandler = determineEventHandler(handler);
        mMainThreadEventHandler = determineMainThreadEventHandler(mEventHandler);

        mService = service;
        if (service != null) {
            mConnectionState = STATE_CONNECTED;
        } else {
            mConnectionState = STATE_DISCONNECTED;
        }
        mServiceConnectionListenerClient = serviceConnectionListener;
        mStatusChangeCallback = statusChangeListener;
        // Store construction stack so that client can get help when it crashes when car service
        // crashes.
        if (serviceConnectionListener == null && statusChangeListener == null) {
            mConstructionStack = new RuntimeException();
        } else {
            mConstructionStack = null;
        }
    }

El componente startCarService()de enlace vinculante anterior :ServiceCarService

    private void startCarService() {
        Intent intent = new Intent();
        intent.setPackage(CAR_SERVICE_PACKAGE);
        intent.setAction(Car.CAR_SERVICE_INTERFACE_NAME);
        boolean bound = mContext.bindServiceAsUser(intent, mServiceConnectionListener,
                Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF);
        synchronized (mLock) {
            if (!bound) {
                mConnectionRetryCount++;
                if (mConnectionRetryCount > CAR_SERVICE_BIND_MAX_RETRY) {
                    Log.w(TAG_CAR, "cannot bind to car service after max retry");
                    mMainThreadEventHandler.post(mConnectionRetryFailedRunnable);
                } else {
                    mEventHandler.postDelayed(mConnectionRetryRunnable,
                            CAR_SERVICE_BIND_RETRY_INTERVAL_MS);
                }
            } else {
                mEventHandler.removeCallbacks(mConnectionRetryRunnable);
                mMainThreadEventHandler.removeCallbacks(mConnectionRetryFailedRunnable);
                mConnectionRetryCount = 0;
                mServiceBound = true;
            }
        }
    }

CarAudioManagerEs principalmente un objeto proxy que se ejecuta en com.android.carel proceso CarAudioServicedel cliente. Su proceso de creación ( paquetes/servicios/Car/car-lib/src/android/car/Car.java ) es el siguiente:

    /**
     * Get car specific service as in {@link Context#getSystemService(String)}. Returned
     * {@link Object} should be type-casted to the desired service.
     * For example, to get sensor service,
     * SensorManagerService sensorManagerService = car.getCarManager(Car.SENSOR_SERVICE);
     * @param serviceName Name of service that should be created like {@link #SENSOR_SERVICE}.
     * @return Matching service manager or null if there is no such service.
     */
    @Nullable
    public Object getCarManager(String serviceName) {
        CarManagerBase manager;
        synchronized (mLock) {
            if (mService == null) {
                Log.w(TAG_CAR, "getCarManager not working while car service not ready");
                return null;
            }
            manager = mServiceMap.get(serviceName);
            if (manager == null) {
                try {
                    IBinder binder = mService.getCarService(serviceName);
                    if (binder == null) {
                        Log.w(TAG_CAR, "getCarManager could not get binder for service:"
                                + serviceName);
                        return null;
                    }
                    manager = createCarManagerLocked(serviceName, binder);
                    if (manager == null) {
                        Log.w(TAG_CAR, "getCarManager could not create manager for service:"
                                        + serviceName);
                        return null;
                    }
                    mServiceMap.put(serviceName, manager);
                } catch (RemoteException e) {
                    handleRemoteExceptionFromCarService(e);
                }
            }
        }
        return manager;
    }
 . . . . . . 
    @Nullable
    private CarManagerBase createCarManagerLocked(String serviceName, IBinder binder) {
        CarManagerBase manager = null;
        switch (serviceName) {
            case AUDIO_SERVICE:
                manager = new CarAudioManager(this, binder);
                break;
            case SENSOR_SERVICE:
                manager = new CarSensorManager(this, binder);
                break;
            case INFO_SERVICE:
                manager = new CarInfoManager(this, binder);
                break;
            case APP_FOCUS_SERVICE:
                manager = new CarAppFocusManager(this, binder);
                break;
            case PACKAGE_SERVICE:
                manager = new CarPackageManager(this, binder);
                break;
            case CAR_OCCUPANT_ZONE_SERVICE:
                manager = new CarOccupantZoneManager(this, binder);
                break;
            case CAR_NAVIGATION_SERVICE:
                manager = new CarNavigationStatusManager(this, binder);
                break;
            case CABIN_SERVICE:
                manager = new CarCabinManager(this, binder);
                break;
            case DIAGNOSTIC_SERVICE:
                manager = new CarDiagnosticManager(this, binder);
                break;
            case HVAC_SERVICE:
                manager = new CarHvacManager(this, binder);
                break;
            case POWER_SERVICE:
                manager = new CarPowerManager(this, binder);
                break;
            case PROJECTION_SERVICE:
                manager = new CarProjectionManager(this, binder);
                break;
            case PROPERTY_SERVICE:
                manager = new CarPropertyManager(this, ICarProperty.Stub.asInterface(binder));
                break;
            case VENDOR_EXTENSION_SERVICE:
                manager = new CarVendorExtensionManager(this, binder);
                break;
            case CAR_INSTRUMENT_CLUSTER_SERVICE:
                manager = new CarInstrumentClusterManager(this, binder);
                break;
            case TEST_SERVICE:
                /* CarTestManager exist in static library. So instead of constructing it here,
                 * only pass binder wrapper so that CarTestManager can be constructed outside. */
                manager = new CarTestManagerBinderWrapper(this, binder);
                break;
            case VEHICLE_MAP_SERVICE:
                manager = new VmsClientManager(this, binder);
                break;
            case VMS_SUBSCRIBER_SERVICE:
                manager = VmsSubscriberManager.wrap(this,
                        (VmsClientManager) getCarManager(VEHICLE_MAP_SERVICE));
                break;
            case BLUETOOTH_SERVICE:
                manager = new CarBluetoothManager(this, binder);
                break;
            case STORAGE_MONITORING_SERVICE:
                manager = new CarStorageMonitoringManager(this, binder);
                break;
            case CAR_DRIVING_STATE_SERVICE:
                manager = new CarDrivingStateManager(this, binder);
                break;
            case CAR_UX_RESTRICTION_SERVICE:
                manager = new CarUxRestrictionsManager(this, binder);
                break;
            case OCCUPANT_AWARENESS_SERVICE:
                manager = new OccupantAwarenessManager(this, binder);
                break;
            case CAR_MEDIA_SERVICE:
                manager = new CarMediaManager(this, binder);
                break;
            case CAR_BUGREPORT_SERVICE:
                manager = new CarBugreportManager(this, binder);
                break;
            case CAR_USER_SERVICE:
                manager = new CarUserManager(this, binder);
                break;
            case CAR_WATCHDOG_SERVICE:
                manager = new CarWatchdogManager(this, binder);
                break;
            case CAR_INPUT_SERVICE:
                manager = new CarInputManager(this, binder);
                break;
            case CAR_DEVICE_POLICY_SERVICE:
                manager = new CarDevicePolicyManager(this, binder);
                break;
            case CLUSTER_HOME_SERVICE:
                manager = new ClusterHomeManager(this, binder);
                break;
            case CAR_EVS_SERVICE:
                manager = new CarEvsManager(this, binder);
                break;
            case CAR_TELEMETRY_SERVICE:
                manager = new CarTelemetryManager(this, binder);
                break;
            case CAR_ACTIVITY_SERVICE:
                manager = new CarActivityManager(this, binder);
                break;
            default:
                // Experimental or non-existing
                String className = null;
                try {
                    className = mService.getCarManagerClassForFeature(serviceName);
                } catch (RemoteException e) {
                    handleRemoteExceptionFromCarService(e);
                    return null;
                }
                if (className == null) {
                    Log.e(TAG_CAR, "Cannot construct CarManager for service:" + serviceName
                            + " : no class defined");
                    return null;
                }
                manager = constructCarManager(className, binder);
                break;
        }
        return manager;
    }

CarObtenga el objeto de carpeta del servicio del administrador correspondiente del servicio "car_service" y cree el administrador correspondiente. También puede ver aquí que AAOS ha creado muchos servicios nuevos.

En el CarServicecódigo anterior, puede ver que la implementación del servicio "car_service" es com.android.car.ICarImpl( paquetes/servicios/Car/service/src/com/android/car/ICarImpl.java ), desde donde puede ver la lógica de obtención específica servicios:

    @Override
    public IBinder getCarService(String serviceName) {
        if (!mFeatureController.isFeatureEnabled(serviceName)) {
            Slog.w(CarLog.TAG_SERVICE, "getCarService for disabled service:" + serviceName);
            return null;
        }
        switch (serviceName) {
            case Car.AUDIO_SERVICE:
                return mCarAudioService;
            case Car.APP_FOCUS_SERVICE:
                return mAppFocusService;
            case Car.PACKAGE_SERVICE:
                return mCarPackageManagerService;
            case Car.DIAGNOSTIC_SERVICE:
                assertAnyDiagnosticPermission(mContext);
                return mCarDiagnosticService;
            case Car.POWER_SERVICE:
                return mCarPowerManagementService;
            case Car.CABIN_SERVICE:
            case Car.HVAC_SERVICE:
            case Car.INFO_SERVICE:
            case Car.PROPERTY_SERVICE:
            case Car.SENSOR_SERVICE:
            case Car.VENDOR_EXTENSION_SERVICE:
                return mCarPropertyService;
            case Car.CAR_NAVIGATION_SERVICE:
                assertNavigationManagerPermission(mContext);
                return mClusterNavigationService;
            case Car.CAR_INSTRUMENT_CLUSTER_SERVICE:
                assertClusterManagerPermission(mContext);
                return mInstrumentClusterService.getManagerService();
            case Car.PROJECTION_SERVICE:
                return mCarProjectionService;
            case Car.VEHICLE_MAP_SERVICE:
                assertAnyVmsPermission(mContext);
                return mVmsBrokerService;
            case Car.VMS_SUBSCRIBER_SERVICE:
                assertVmsSubscriberPermission(mContext);
                return mVmsBrokerService;
            case Car.TEST_SERVICE: {
                assertPermission(mContext, Car.PERMISSION_CAR_TEST_SERVICE);
                synchronized (mLock) {
                    if (mCarTestService == null) {
                        mCarTestService = new CarTestService(mContext, this);
                    }
                    return mCarTestService;
                }
            }
            case Car.BLUETOOTH_SERVICE:
                return mCarBluetoothService;
            case Car.STORAGE_MONITORING_SERVICE:
                assertPermission(mContext, Car.PERMISSION_STORAGE_MONITORING);
                return mCarStorageMonitoringService;
            case Car.CAR_DRIVING_STATE_SERVICE:
                assertDrivingStatePermission(mContext);
                return mCarDrivingStateService;
            case Car.CAR_UX_RESTRICTION_SERVICE:
                return mCarUXRestrictionsService;
            case Car.OCCUPANT_AWARENESS_SERVICE:
                return mOccupantAwarenessService;
            case Car.CAR_MEDIA_SERVICE:
                return mCarMediaService;
            case Car.CAR_OCCUPANT_ZONE_SERVICE:
                return mCarOccupantZoneService;
            case Car.CAR_BUGREPORT_SERVICE:
                return mCarBugreportManagerService;
            case Car.CAR_USER_SERVICE:
                return mCarUserService;
            case Car.CAR_WATCHDOG_SERVICE:
                return mCarWatchdogService;
            case Car.CAR_INPUT_SERVICE:
                return mCarInputService;
            case Car.CAR_DEVICE_POLICY_SERVICE:
                return mCarDevicePolicyService;
            case Car.CLUSTER_HOME_SERVICE:
                return mClusterHomeService;
            case Car.CAR_EVS_SERVICE:
                return mCarEvsService;
            case Car.CAR_TELEMETRY_SERVICE:
                return mCarTelemetryService;
            case Car.CAR_ACTIVITY_SERVICE:
                return mCarActivityService;
            default:
                IBinder service = null;
                if (mCarExperimentalFeatureServiceController != null) {
                    service = mCarExperimentalFeatureServiceController.getCarService(serviceName);
                }
                if (service == null) {
                    Slog.w(CarLog.TAG_SERVICE, "getCarService for unknown service:"
                            + serviceName);
                }
                return service;
        }
    }

En resumen, es principalmente el objeto proxy del servicio "car_service" Carque se ejecuta en el proceso en el lado del cliente, y es principalmente el objeto proxy del servicio que se ejecuta en el mismo proceso en el lado del cliente. Aunque el nombre del servicio pasado mediante create es el mismo que el nombre del servicio pasado mediante get , son servicios completamente diferentes. Como se muestra abajo:com.android.carCarAudioManagerCarAudioServiceCarCarAudioManagerContextgetSystemService()AudioManager

Coche y CarAudioManager

CarAudioManageroperaciones de audio

A continuación se muestra CarAudioManagerla implementación de operaciones de audio ( paquetes/servicios/Car/car-lib/src/android/car/media/CarAudioManager.java ):

    private final ICarVolumeCallback mCarVolumeCallbackImpl =
            new android.car.media.ICarVolumeCallback.Stub() {
        @Override
        public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
            mEventHandler.dispatchOnGroupVolumeChanged(zoneId, groupId, flags);
        }

        @Override
        public void onGroupMuteChanged(int zoneId, int groupId, int flags) {
            mEventHandler.dispatchOnGroupMuteChanged(zoneId, groupId, flags);
        }

        @Override
        public void onMasterMuteChanged(int zoneId, int flags) {
            mEventHandler.dispatchOnMasterMuteChanged(zoneId, flags);
        }
    };
 . . . . . .
    /**
     * Sets the volume index for a volume group in primary zone.
     *
     * @see {@link #setGroupVolume(int, int, int, int)}
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public void setGroupVolume(int groupId, int index, int flags) {
        setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, index, flags);
    }

    /**
     * Sets the volume index for a volume group.
     *
     * @param zoneId The zone id whose volume group is affected.
     * @param groupId The volume group id whose volume index should be set.
     * @param index The volume index to set. See
     *            {@link #getGroupMaxVolume(int, int)} for the largest valid value.
     * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI},
     *              {@link android.media.AudioManager#FLAG_PLAY_SOUND})
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
        try {
            mService.setGroupVolume(zoneId, groupId, index, flags);
        } catch (RemoteException e) {
            handleRemoteExceptionFromCarService(e);
        }
    }

    /**
     * Returns the maximum volume index for a volume group in primary zone.
     *
     * @see {@link #getGroupMaxVolume(int, int)}
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupMaxVolume(int groupId) {
        return getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId);
    }

    /**
     * Returns the maximum volume index for a volume group.
     *
     * @param zoneId The zone id whose volume group is queried.
     * @param groupId The volume group id whose maximum volume index is returned.
     * @return The maximum valid volume index for the given group.
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupMaxVolume(int zoneId, int groupId) {
        try {
            return mService.getGroupMaxVolume(zoneId, groupId);
        } catch (RemoteException e) {
            return handleRemoteExceptionFromCarService(e, 0);
        }
    }

    /**
     * Returns the minimum volume index for a volume group in primary zone.
     *
     * @see {@link #getGroupMinVolume(int, int)}
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupMinVolume(int groupId) {
        return getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
    }

    /**
     * Returns the minimum volume index for a volume group.
     *
     * @param zoneId The zone id whose volume group is queried.
     * @param groupId The volume group id whose minimum volume index is returned.
     * @return The minimum valid volume index for the given group, non-negative
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupMinVolume(int zoneId, int groupId) {
        try {
            return mService.getGroupMinVolume(zoneId, groupId);
        } catch (RemoteException e) {
            return handleRemoteExceptionFromCarService(e, 0);
        }
    }

    /**
     * Returns the current volume index for a volume group in primary zone.
     *
     * @see {@link #getGroupVolume(int, int)}
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupVolume(int groupId) {
        return getGroupVolume(PRIMARY_AUDIO_ZONE, groupId);
    }

    /**
     * Returns the current volume index for a volume group.
     *
     * @param zoneId The zone id whose volume groups is queried.
     * @param groupId The volume group id whose volume index is returned.
     * @return The current volume index for the given group.
     *
     * @see #getGroupMaxVolume(int, int)
     * @see #setGroupVolume(int, int, int, int)
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupVolume(int zoneId, int groupId) {
        try {
            return mService.getGroupVolume(zoneId, groupId);
        } catch (RemoteException e) {
            return handleRemoteExceptionFromCarService(e, 0);
        }
    }
 . . . . . .
    /**
     * Gets the count of available volume groups in primary zone.
     *
     * @see {@link #getVolumeGroupCount(int)}
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getVolumeGroupCount() {
        return getVolumeGroupCount(PRIMARY_AUDIO_ZONE);
    }

    /**
     * Gets the count of available volume groups in the system.
     *
     * @param zoneId The zone id whois count of volume groups is queried.
     * @return Count of volume groups
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getVolumeGroupCount(int zoneId) {
        try {
            return mService.getVolumeGroupCount(zoneId);
        } catch (RemoteException e) {
            return handleRemoteExceptionFromCarService(e, 0);
        }
    }
 . . . . . .
    /**
     * Gets array of {@link AudioAttributes} usages for a volume group in primary zone.
     *
     * @see {@link #getUsagesForVolumeGroupId(int, int)}
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public @NonNull int[] getUsagesForVolumeGroupId(int groupId) {
        return getUsagesForVolumeGroupId(PRIMARY_AUDIO_ZONE, groupId);
    }

    /**
     * Gets array of {@link AudioAttributes} usages for a volume group in a zone.
     *
     * @param zoneId The zone id whose volume group is queried.
     * @param groupId The volume group id whose associated audio usages is returned.
     * @return Array of {@link AudioAttributes} usages for a given volume group id
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
        try {
            return mService.getUsagesForVolumeGroupId(zoneId, groupId);
        } catch (RemoteException e) {
            return handleRemoteExceptionFromCarService(e, new int[0]);
        }
    }
 . . . . . .
    /** @hide */
    public CarAudioManager(Car car, IBinder service) {
        super(car);
        mService = ICarAudio.Stub.asInterface(service);
        mAudioManager = getContext().getSystemService(AudioManager.class);
        mCarVolumeCallbacks = new CopyOnWriteArrayList<>();
        mEventHandler = new EventHandler(getEventHandler().getLooper());
    }

    /**
     * Registers a {@link CarVolumeCallback} to receive volume change callbacks
     * @param callback {@link CarVolumeCallback} instance, can not be null
     * <p>
     * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME
     */
    public void registerCarVolumeCallback(@NonNull CarVolumeCallback callback) {
        Objects.requireNonNull(callback);

        if (mCarVolumeCallbacks.isEmpty()) {
            registerVolumeCallback();
        }

        mCarVolumeCallbacks.add(callback);
    }

    /**
     * Unregisters a {@link CarVolumeCallback} from receiving volume change callbacks
     * @param callback {@link CarVolumeCallback} instance previously registered, can not be null
     * <p>
     * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME
     */
    public void unregisterCarVolumeCallback(@NonNull CarVolumeCallback callback) {
        Objects.requireNonNull(callback);
        if (mCarVolumeCallbacks.remove(callback) && mCarVolumeCallbacks.isEmpty()) {
            unregisterVolumeCallback();
        }
    }

    private void registerVolumeCallback() {
        try {
            mService.registerVolumeCallback(mCarVolumeCallbackImpl.asBinder());
        } catch (RemoteException e) {
            Log.e(CarLibLog.TAG_CAR, "registerVolumeCallback failed", e);
        }
    }

    private void unregisterVolumeCallback() {
        try {
            mService.unregisterVolumeCallback(mCarVolumeCallbackImpl.asBinder());
        } catch (RemoteException e) {
            handleRemoteExceptionFromCarService(e);
        }
    }

CarAudioManagerBásicamente, las operaciones de audio eventualmente se transferirán a CarAudioServiceejecución. A continuación se muestra la implementación de varias operaciones de audio en CarAudioService.

Obtenga la cantidad de grupos de volumen en la zona de sonido

La obtención del número de grupos de audio en la zona de sonido se completa CarAudioManagercon getVolumeGroupCount()y getVolumeGroupCount(int zoneId). CarAudioServiceEl proceso detallado realmente ejecutado en ( paquetes /services/Car/service/src/com/android/car/audio/CarAudioService.java ) es el siguiente:

    @GuardedBy("mImplLock")
    private SparseArray<CarAudioZone> mCarAudioZones;
 . . . . . .
    @Override
    public int getVolumeGroupCount(int zoneId) {
        synchronized (mImplLock) {
            enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
            // For legacy stream type based volume control
            if (!mUseDynamicRouting) return CarAudioDynamicRouting.STREAM_TYPES.length;

            return getCarAudioZoneLocked(zoneId).getVolumeGroupCount();
        }
    }
 . . . . . .
    @GuardedBy("mImplLock")
    private CarAudioZone getCarAudioZoneLocked(int zoneId) {
        checkAudioZoneIdLocked(zoneId);
        return mCarAudioZones.get(zoneId);
    }

Hay dos situaciones para obtener el número de grupos de volumen en la zona de sonido. Una es cuando no se utiliza el enrutamiento dinámico. En este caso, siempre se devuelve un valor fijo, es decir, el número de tipos de flujo. La segunda es cuando el número de grupos de volumen en la zona de sonido es dinámico. Se utiliza enrutamiento. En este caso, de acuerdo con el CarAudioServicesonido, el ID de zona obtiene la información de descripción de la zona de sonido de un conjunto de tablas de información de zonas de sonido precargadas CarAudioZoney obtiene el número de grupos de volumen en la zona de sonido.

El uso del enrutamiento dinámico está determinado por el atributo "audioUseDynamicRouting" en el archivo config.xml :

    public CarAudioService(Context context) {
        mContext = context;
        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

        mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);

El valor predeterminado de esta propiedad en el archivo paquetes/servicios/Car/service/res/values/config.xmlfalse es . Pero para el simulador, device/generic/car/emulator/audio/overlay/packages/services/Car/service/res/values/config.xmlel archivo del dispositivo anulará el valor predeterminado true. La información de la zona se carga CarAudioServicedurante la inicialización ( init()-> setupDynamicRoutingLocked()-> loadCarAudioZonesLocked()):

    @GuardedBy("mImplLock")
    private void loadCarAudioZonesLocked() {
        List<CarAudioDeviceInfo> carAudioDeviceInfos = generateCarAudioDeviceInfos();
        AudioDeviceInfo[] inputDevices = getAllInputDevices();

        mCarAudioConfigurationPath = getAudioConfigurationPath();
        if (mCarAudioConfigurationPath != null) {
            mCarAudioZones = loadCarAudioConfigurationLocked(carAudioDeviceInfos, inputDevices);
        } else {
            mCarAudioZones =
                    loadVolumeGroupConfigurationWithAudioControlLocked(carAudioDeviceInfos,
                            inputDevices);
        }

        CarAudioZonesValidator.validate(mCarAudioZones);
    }

El proceso de carga de información de zona es el siguiente:

  1. Obtenga información sobre todos los dispositivos de entrada y salida;
  2. Obtenga la ruta al archivo de configuración de audio del automóvil;
  3. Cuando se obtiene una ruta de archivo de configuración de audio del vehículo válida, la información de la zona de sonido se carga desde el archivo;
  4. Cuando no se puede obtener una ruta de archivo de configuración de audio del vehículo válida, la información de la zona de sonido se carga desde el servicio de control de audio de HAL;
  5. Verificar la información del registro.

CarAudioServiceAudioManagerObtenga información sobre dispositivos de entrada y salida de :

    private List<CarAudioDeviceInfo> generateCarAudioDeviceInfos() {
        AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
                AudioManager.GET_DEVICES_OUTPUTS);

        return Arrays.stream(deviceInfos)
                .filter(info -> info.getType() == AudioDeviceInfo.TYPE_BUS)
                .map(CarAudioDeviceInfo::new)
                .collect(Collectors.toList());
    }

    private AudioDeviceInfo[] getAllInputDevices() {
        return mAudioManager.getDevices(
                AudioManager.GET_DEVICES_INPUTS);
    }

CarAudioServiceComprueba dos ubicaciones predefinidas para encontrar la ruta al archivo de configuración de audio:

    // CarAudioService reads configuration from the following paths respectively.
    // If the first one is found, all others are ignored.
    // If no one is found, it fallbacks to car_volume_groups.xml resource file.
    private static final String[] AUDIO_CONFIGURATION_PATHS = new String[] {
            "/vendor/etc/car_audio_configuration.xml",
            "/system/etc/car_audio_configuration.xml"
    };
 . . . . . .
    /**
     * Read from {@link #AUDIO_CONFIGURATION_PATHS} respectively.
     * @return File path of the first hit in {@link #AUDIO_CONFIGURATION_PATHS}
     */
    @Nullable
    private String getAudioConfigurationPath() {
        for (String path : AUDIO_CONFIGURATION_PATHS) {
            File configuration = new File(path);
            if (configuration.exists()) {
                return path;
            }
        }
        return null;
    }

Cada ruta en la lista de rutas del perfil de audio, ordenada por prioridad, CarAudioServiceutilizará el archivo existente más antiguo que se encuentre en la lista. Para el simulador, el archivo /system/etc/car_audio_configuration.xml no existe, pero sí existe /vendor/etc/car_audio_configuration.xml , que proviene de device/generic/car/emulator/audio/car_audio_configuration.xml . este archivo para:

<!--
  Defines the audio configuration in a car, including
    - Audio zones
    - Context to audio bus mappings
    - Volume groups
  in the car environment.
-->
<carAudioConfiguration version="2">
    <zones>
        <zone name="primary zone" isPrimary="true" occupantZoneId="0">
            <volumeGroups>
                <group>
                    <device address="bus0_media_out">
                        <context context="music"/>
                        <context context="announcement"/>
                    </device>
                    <device address="bus6_notification_out">
                        <context context="notification"/>
                    </device>
                </group>
                <group>
                    <device address="bus1_navigation_out">
                        <context context="navigation"/>
                    </device>
                    <device address="bus2_voice_command_out">
                        <context context="voice_command"/>
                    </device>
                </group>
                <group>
                    <device address="bus4_call_out">
                        <context context="call"/>
                    </device>
                    <device address="bus3_call_ring_out">
                        <context context="call_ring"/>
                    </device>
                </group>
                <group>
                    <device address="bus5_alarm_out">
                        <context context="alarm"/>
                    </device>
                    <device address="bus7_system_sound_out">
                        <context context="system_sound"/>
                        <context context="emergency"/>
                        <context context="safety"/>
                        <context context="vehicle_status"/>
                    </device>
                </group>
            </volumeGroups>
        </zone>
        <zone name="rear seat zone 1" audioZoneId="1">
            <volumeGroups>
                <group>
                    <device address="bus100_audio_zone_1">
                        <context context="music"/>
                        <context context="navigation"/>
                        <context context="voice_command"/>
                        <context context="call_ring"/>
                        <context context="call"/>
                        <context context="alarm"/>
                        <context context="notification"/>
                        <context context="system_sound"/>
                        <context context="emergency"/>
                        <context context="safety"/>
                        <context context="vehicle_status"/>
                        <context context="announcement"/>
                    </device>
                </group>
            </volumeGroups>
        </zone>
        <zone name="rear seat zone 2"  audioZoneId="2">
            <volumeGroups>
                <group>
                    <device address="bus200_audio_zone_2">
                        <context context="music"/>
                        <context context="navigation"/>
                        <context context="voice_command"/>
                        <context context="call_ring"/>
                        <context context="call"/>
                        <context context="alarm"/>
                        <context context="notification"/>
                        <context context="system_sound"/>
                        <context context="emergency"/>
                        <context context="safety"/>
                        <context context="vehicle_status"/>
                        <context context="announcement"/>
                    </device>
                </group>
            </volumeGroups>
        </zone>
    </zones>
</carAudioConfiguration>

CarAudioServicePrincipalmente con la ayuda de CarAudioZonesHelpercargar información de registro desde un archivo:

    @GuardedBy("mImplLock")
    private SparseArray<CarAudioZone> loadCarAudioConfigurationLocked(
            List<CarAudioDeviceInfo> carAudioDeviceInfos, AudioDeviceInfo[] inputDevices) {
        try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
            CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mCarAudioSettings,
                    inputStream, carAudioDeviceInfos, inputDevices, mUseCarVolumeGroupMuting);
            mAudioZoneIdToOccupantZoneIdMapping =
                    zonesHelper.getCarAudioZoneIdToOccupantZoneIdMapping();
            return zonesHelper.loadAudioZones();
        } catch (IOException | XmlPullParserException e) {
            throw new RuntimeException("Failed to parse audio zone configuration", e);
        }
    }

Este proceso establece principalmente el contexto y el dispositivo de audio de salida de la zona de sonido, el grupo de audio y el grupo de audio, así como la asociación entre la zona de sonido y su dispositivo de entrada. El dispositivo de entrada no está definido en car_audio_configuration.xml del simulador que vimos anteriormente , pero CarAudioServiceadmite la definición de dispositivos de audio de entrada para zonas de sonido. Para los dispositivos de entrada de audio que no tienen su propia zona de sonido definida en car_audio_configuration.xml , se colocarán en la zona de sonido principal. Las variables miembro que describen la región del sonido CarAudioZoneincluyen principalmente las siguientes:

/* package */ class CarAudioZone {

    private final int mId;
    private final String mName;
    private final List<CarVolumeGroup> mVolumeGroups;
    private final Set<String> mDeviceAddresses;
    private List<AudioDeviceAttributes> mInputAudioDevice;

La asociación entre la zona de sonido, el grupo de audio y el contexto del grupo de audio y el dispositivo de audio de salida se basa principalmente en CarVolumeGroupla descripción, y la asociación entre la zona de sonido y su dispositivo de entrada se basa principalmente en AudioDeviceAttributesla descripción.

Cuando no se puede obtener la ruta válida del archivo de configuración de audio del vehículo, la información del grupo de volumen se carga a través del servicio de control de audio hal:

    @GuardedBy("mImplLock")
    private SparseArray<CarAudioZone> loadVolumeGroupConfigurationWithAudioControlLocked(
            List<CarAudioDeviceInfo> carAudioDeviceInfos, AudioDeviceInfo[] inputDevices) {
        AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
        if (!(audioControlWrapper instanceof AudioControlWrapperV1)) {
            throw new IllegalStateException(
                    "Updated version of IAudioControl no longer supports CarAudioZonesHelperLegacy."
                    + " Please provide car_audio_configuration.xml.");
        }
        CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
                R.xml.car_volume_groups, carAudioDeviceInfos,
                (AudioControlWrapperV1) audioControlWrapper, mCarAudioSettings, inputDevices);
        return legacyHelper.loadAudioZones();
    }

En este momento, solo hay una zona de sonido principal y car_volume_groups.xmlla información del grupo de volumen se cargará desde Este proceso de carga se CarAudioZonesHelperLegacycompleta principalmente con la ayuda de.

car_volume_groups.xmlEl contenido es más sencillo:

<volumeGroups xmlns:car="http://schemas.android.com/apk/res-auto"
        car:isDeprecated="true">
    <group>
        <context car:context="music"/>
        <context car:context="call_ring"/>
        <context car:context="notification"/>
        <context car:context="system_sound"/>
    </group>
    <group>
        <context car:context="navigation"/>
        <context car:context="voice_command"/>
    </group>
    <group>
        <context car:context="call"/>
    </group>
    <group>
        <context car:context="alarm"/>
    </group>
</volumeGroups>

car_volume_groups.xmlContiene el contexto contenido en cada grupo de volúmenes. La AudioManagerdirección obtenida de cada dispositivo de entrada y salida de audio contiene la información del bus del dispositivo. Puede obtener su bus correspondiente del servicio de control de audio de acuerdo con el contexto, de modo que el grupo de volúmenes y el contexto están y los dispositivos correspondientes están asociados.

Después de tener la información de la zona de sonido y el grupo de volumen, lo lógico es obtener el número de grupos de volumen para una zona de sonido específica.

Obtener el uso del grupo de volúmenes de la zona.

Esta operación se realiza CarAudioManagermediante getUsagesForVolumeGroupId():

    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public @NonNull int[] getUsagesForVolumeGroupId(int groupId) {
        return getUsagesForVolumeGroupId(PRIMARY_AUDIO_ZONE, groupId);
    }

    /**
     * Gets array of {@link AudioAttributes} usages for a volume group in a zone.
     *
     * @param zoneId The zone id whose volume group is queried.
     * @param groupId The volume group id whose associated audio usages is returned.
     * @return Array of {@link AudioAttributes} usages for a given volume group id
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
        try {
            return mService.getUsagesForVolumeGroupId(zoneId, groupId);
        } catch (RemoteException e) {
            return handleRemoteExceptionFromCarService(e, new int[0]);
        }
    }

La implementación de esta operación CarAudioServicees la siguiente:

    @Override
    public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
        synchronized (mImplLock) {
            enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);

            // For legacy stream type based volume control
            if (!mUseDynamicRouting) {
                return new int[] { CarAudioDynamicRouting.STREAM_TYPE_USAGES[groupId] };
            }

            CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
            Set<Integer> contexts =
                    Arrays.stream(group.getContexts()).boxed().collect(Collectors.toSet());
            final List<Integer> usages = new ArrayList<>();
            for (@AudioContext int context : contexts) {
                int[] usagesForContext = CarAudioContext.getUsagesForContext(context);
                for (@AttributeUsage int usage : usagesForContext) {
                    usages.add(usage);
                }
            }
            return usages.stream().mapToInt(i -> i).toArray();
        }
    }

CarAudioServiceLo que se mantiene es la información de contexto del grupo de audio de la zona de sonido . Al obtener el uso del grupo de volumen de la zona de sonido, primero obtenga su contexto, luego obtenga el uso correspondiente al contexto de acuerdo con el mapeo creado previamente, combine el uso correspondiente a todos los contextos del grupo de volumen y devuélvalo.

El mapeo del contexto al uso CarAudioContext.getUsagesForContext(context)se completa con:

    private static final SparseArray<int[]> CONTEXT_TO_USAGES = new SparseArray<>();

    static {
        CONTEXT_TO_USAGES.put(MUSIC,
                new int[]{
                        AudioAttributes.USAGE_UNKNOWN,
                        AudioAttributes.USAGE_GAME,
                        AudioAttributes.USAGE_MEDIA
                });

        CONTEXT_TO_USAGES.put(NAVIGATION,
                new int[]{
                        AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
                });

        CONTEXT_TO_USAGES.put(VOICE_COMMAND,
                new int[]{
                        AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
                        AudioAttributes.USAGE_ASSISTANT
                });

        CONTEXT_TO_USAGES.put(CALL_RING,
                new int[]{
                        AudioAttributes.USAGE_NOTIFICATION_RINGTONE
                });

        CONTEXT_TO_USAGES.put(CALL,
                new int[]{
                        AudioAttributes.USAGE_VOICE_COMMUNICATION,
                        AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING
                });

        CONTEXT_TO_USAGES.put(ALARM,
                new int[]{
                        AudioAttributes.USAGE_ALARM
                });

        CONTEXT_TO_USAGES.put(NOTIFICATION,
                new int[]{
                        AudioAttributes.USAGE_NOTIFICATION,
                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
                        AudioAttributes.USAGE_NOTIFICATION_EVENT
                });

        CONTEXT_TO_USAGES.put(SYSTEM_SOUND,
                new int[]{
                        AudioAttributes.USAGE_ASSISTANCE_SONIFICATION
                });

        CONTEXT_TO_USAGES.put(EMERGENCY,
                new int[]{
                        AudioAttributes.USAGE_EMERGENCY
                });

        CONTEXT_TO_USAGES.put(SAFETY,
                new int[]{
                        AudioAttributes.USAGE_SAFETY
                });

        CONTEXT_TO_USAGES.put(VEHICLE_STATUS,
                new int[]{
                        AudioAttributes.USAGE_VEHICLE_STATUS
                });

        CONTEXT_TO_USAGES.put(ANNOUNCEMENT,
                new int[]{
                        AudioAttributes.USAGE_ANNOUNCEMENT
                });

        CONTEXT_TO_USAGES.put(INVALID,
                new int[]{
                        AudioAttributes.USAGE_VIRTUAL_SOURCE
                });
    }
 . . . . . .
    static @AttributeUsage int[] getUsagesForContext(@AudioContext int carAudioContext) {
        preconditionCheckAudioContext(carAudioContext);
        return CONTEXT_TO_USAGES.get(carAudioContext);
    }

De esta forma se obtiene el aprovechamiento del grupo de volumen de la zona de sonido.

Obtener el volumen del grupo de volumen de la zona.

Obtener el volumen del grupo de volúmenes de la zona de sonido, incluyendo la obtención de su valor mínimo, valor máximo y valor actual. Estas operaciones CarAudioManagerse realizan con los siguientes métodos:

    /**
     * Returns the maximum volume index for a volume group in primary zone.
     *
     * @see {@link #getGroupMaxVolume(int, int)}
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupMaxVolume(int groupId) {
        return getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId);
    }

    /**
     * Returns the maximum volume index for a volume group.
     *
     * @param zoneId The zone id whose volume group is queried.
     * @param groupId The volume group id whose maximum volume index is returned.
     * @return The maximum valid volume index for the given group.
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupMaxVolume(int zoneId, int groupId) {
        try {
            return mService.getGroupMaxVolume(zoneId, groupId);
        } catch (RemoteException e) {
            return handleRemoteExceptionFromCarService(e, 0);
        }
    }

    /**
     * Returns the minimum volume index for a volume group in primary zone.
     *
     * @see {@link #getGroupMinVolume(int, int)}
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupMinVolume(int groupId) {
        return getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
    }

    /**
     * Returns the minimum volume index for a volume group.
     *
     * @param zoneId The zone id whose volume group is queried.
     * @param groupId The volume group id whose minimum volume index is returned.
     * @return The minimum valid volume index for the given group, non-negative
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupMinVolume(int zoneId, int groupId) {
        try {
            return mService.getGroupMinVolume(zoneId, groupId);
        } catch (RemoteException e) {
            return handleRemoteExceptionFromCarService(e, 0);
        }
    }

    /**
     * Returns the current volume index for a volume group in primary zone.
     *
     * @see {@link #getGroupVolume(int, int)}
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupVolume(int groupId) {
        return getGroupVolume(PRIMARY_AUDIO_ZONE, groupId);
    }

    /**
     * Returns the current volume index for a volume group.
     *
     * @param zoneId The zone id whose volume groups is queried.
     * @param groupId The volume group id whose volume index is returned.
     * @return The current volume index for the given group.
     *
     * @see #getGroupMaxVolume(int, int)
     * @see #setGroupVolume(int, int, int, int)
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public int getGroupVolume(int zoneId, int groupId) {
        try {
            return mService.getGroupVolume(zoneId, groupId);
        } catch (RemoteException e) {
            return handleRemoteExceptionFromCarService(e, 0);
        }
    }

CarAudioServiceLas implementaciones correspondientes de estos métodos son las siguientes

    /**
     * @see {@link android.car.media.CarAudioManager#getGroupMaxVolume(int, int)}
     */
    @Override
    public int getGroupMaxVolume(int zoneId, int groupId) {
        synchronized (mImplLock) {
            enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);

            // For legacy stream type based volume control
            if (!mUseDynamicRouting) {
                return mAudioManager.getStreamMaxVolume(
                        CarAudioDynamicRouting.STREAM_TYPES[groupId]);
            }

            CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
            return group.getMaxGainIndex();
        }
    }

    /**
     * @see {@link android.car.media.CarAudioManager#getGroupMinVolume(int, int)}
     */
    @Override
    public int getGroupMinVolume(int zoneId, int groupId) {
        synchronized (mImplLock) {
            enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);

            // For legacy stream type based volume control
            if (!mUseDynamicRouting) {
                return mAudioManager.getStreamMinVolume(
                        CarAudioDynamicRouting.STREAM_TYPES[groupId]);
            }

            CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
            return group.getMinGainIndex();
        }
    }

    /**
     * @see {@link android.car.media.CarAudioManager#getGroupVolume(int, int)}
     */
    @Override
    public int getGroupVolume(int zoneId, int groupId) {
        synchronized (mImplLock) {
            enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);

            // For legacy stream type based volume control
            if (!mUseDynamicRouting) {
                return mAudioManager.getStreamVolume(
                        CarAudioDynamicRouting.STREAM_TYPES[groupId]);
            }

            CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
            return group.getCurrentGainIndex();
        }
    }

Cuando no se utiliza el enrutamiento dinámico, no existe el concepto de zonas de sonido integradas y la información de volumen no se CarAudioServicemantiene. Estos métodos asignan el grupo de volúmenes a STREAM_TYPE y AudioManagerobtienen el volumen correspondiente.

Cuando se utiliza el enrutamiento dinámico, la información del volumen CarAudioServicese mantiene. Estos métodos obtienen la información del grupo de volúmenes según la ID de zona y la ID del grupo de volúmenes, y packages/services/Car/service/src/com/android/car/audio/CarVolumeGroup.javaobtienen el volumen correspondiente del grupo de volúmenes ():

    int getMaxGainIndex() {
        synchronized (mLock) {
            return getIndexForGain(mMaxGain);
        }
    }

    int getMinGainIndex() {
        synchronized (mLock) {
            return getIndexForGain(mMinGain);
        }
    }

    int getCurrentGainIndex() {
        synchronized (mLock) {
            if (mIsMuted) {
                return getIndexForGain(mMinGain);
            }
            return getCurrentGainIndexLocked();
        }
    }

    private int getCurrentGainIndexLocked() {
        return mCurrentGainIndex;
    }
 . . . . . .
    private int getIndexForGain(int gainInMillibel) {
        return (gainInMillibel - mMinGain) / mStepSize;
    }

Los valores de volumen máximo y mínimo del grupo de volumen de la zona de sonido y el valor del paso durante el ajuste provienen del dispositivo:

    private CarVolumeGroup(int zoneId, int id, CarAudioSettings settingsManager, int stepSize,
            int defaultGain, int minGain, int maxGain, SparseArray<String> contextToAddress,
            Map<String, CarAudioDeviceInfo> addressToCarAudioDeviceInfo,
            boolean useCarVolumeGroupMute) {

        mSettingsManager = settingsManager;
        mZoneId = zoneId;
        mId = id;
        mStepSize = stepSize;
        mDefaultGain = defaultGain;
        mMinGain = minGain;
        mMaxGain = maxGain;
        mContextToAddress = contextToAddress;
        mAddressToCarAudioDeviceInfo = addressToCarAudioDeviceInfo;
        mUseCarVolumeGroupMute = useCarVolumeGroupMute;

        mHasCriticalAudioContexts = containsCriticalAudioContext(contextToAddress);
    }
 . . . . . .
    static final class Builder {
        private static final int UNSET_STEP_SIZE = -1;

        private final int mId;
        private final int mZoneId;
        private final boolean mUseCarVolumeGroupMute;
        private final CarAudioSettings mCarAudioSettings;
        private final SparseArray<String> mContextToAddress = new SparseArray<>();
        private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo =
                new HashMap<>();

        @VisibleForTesting
        int mStepSize = UNSET_STEP_SIZE;
        @VisibleForTesting
        int mDefaultGain = Integer.MIN_VALUE;
        @VisibleForTesting
        int mMaxGain = Integer.MIN_VALUE;
        @VisibleForTesting
        int mMinGain = Integer.MAX_VALUE;

        Builder(int zoneId, int id, CarAudioSettings carAudioSettings,
                boolean useCarVolumeGroupMute) {
            mZoneId = zoneId;
            mId = id;
            mCarAudioSettings = carAudioSettings;
            mUseCarVolumeGroupMute = useCarVolumeGroupMute;
        }

        Builder setDeviceInfoForContext(int carAudioContext, CarAudioDeviceInfo info) {
            Preconditions.checkArgument(mContextToAddress.get(carAudioContext) == null,
                    "Context %s has already been set to %s",
                    CarAudioContext.toString(carAudioContext),
                    mContextToAddress.get(carAudioContext));

            if (mAddressToCarAudioDeviceInfo.isEmpty()) {
                mStepSize = info.getStepValue();
            } else {
                Preconditions.checkArgument(
                        info.getStepValue() == mStepSize,
                        "Gain controls within one group must have same step value");
            }

            mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
            mContextToAddress.put(carAudioContext, info.getAddress());

            if (info.getDefaultGain() > mDefaultGain) {
                // We're arbitrarily selecting the highest
                // device default gain as the group's default.
                mDefaultGain = info.getDefaultGain();
            }
            if (info.getMaxGain() > mMaxGain) {
                mMaxGain = info.getMaxGain();
            }
            if (info.getMinGain() < mMinGain) {
                mMinGain = info.getMinGain();
            }

            return this;
        }

        CarVolumeGroup build() {
            Preconditions.checkArgument(mStepSize != UNSET_STEP_SIZE,
                    "setDeviceInfoForContext has to be called at least once before building");
            CarVolumeGroup group = new CarVolumeGroup(mZoneId, mId, mCarAudioSettings, mStepSize,
                    mDefaultGain, mMinGain, mMaxGain, mContextToAddress,
                    mAddressToCarAudioDeviceInfo, mUseCarVolumeGroupMute);
            group.init();
            return group;
        }
    }

El valor predeterminado de volumen también proviene del dispositivo.

Se intentará obtener el valor de volumen actual desde Configuración durante la inicialización. Si no se puede obtener, se utilizará el valor predeterminado:

    void init() {
        mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, mId);
        updateCurrentGainIndexLocked();
    }
 . . . . . .
    @GuardedBy("mLock")
    private void updateCurrentGainIndexLocked() {
        if (isValidGainIndex(mStoredGainIndex)) {
            mCurrentGainIndex = mStoredGainIndex;
        } else {
            mCurrentGainIndex = getIndexForGain(mDefaultGain);
        }
    }

El método para obtener el valor del volumen desde Configuración ( packages/services/Car/service/src/com/android/car/audio/CarAudioSettings.java) es el siguiente:

    int getStoredVolumeGainIndexForUser(int userId, int zoneId, int groupId) {
        return Settings.System.getIntForUser(mContentResolver,
                getVolumeSettingsKeyForGroup(zoneId, groupId), -1, userId);
    }

Establecer el volumen del grupo de volumen de la zona.

La configuración del volumen del grupo de volumen de una zona CarAudioManagerse logra mediante los siguientes métodos:

    /**
     * Sets the volume index for a volume group in primary zone.
     *
     * @see {@link #setGroupVolume(int, int, int, int)}
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public void setGroupVolume(int groupId, int index, int flags) {
        setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, index, flags);
    }

    /**
     * Sets the volume index for a volume group.
     *
     * @param zoneId The zone id whose volume group is affected.
     * @param groupId The volume group id whose volume index should be set.
     * @param index The volume index to set. See
     *            {@link #getGroupMaxVolume(int, int)} for the largest valid value.
     * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI},
     *              {@link android.media.AudioManager#FLAG_PLAY_SOUND})
     * @hide
     */
    @SystemApi
    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
        try {
            mService.setGroupVolume(zoneId, groupId, index, flags);
        } catch (RemoteException e) {
            handleRemoteExceptionFromCarService(e);
        }
    }

El volumen aquí se describe por índice.

En CarAudioService, esta operación se implementa de la siguiente manera:

    /**
     * @see {@link android.car.media.CarAudioManager#setGroupVolume(int, int, int, int)}
     */
    @Override
    public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
        callbackGroupVolumeChange(zoneId, groupId, flags);
        // For legacy stream type based volume control
        if (!mUseDynamicRouting) {
            mAudioManager.setStreamVolume(
                    CarAudioDynamicRouting.STREAM_TYPES[groupId], index, flags);
            return;
        }
        synchronized (mImplLock) {
            CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
            group.setCurrentGainIndex(index);
        }
    }

También hay dos situaciones aquí. Una es cuando no se utiliza el enrutamiento dinámico. En este caso, el ID del grupo de volúmenes se asigna a STREAM_TYPE y el volumen está configurado para STREAM_TYPE. La segunda es cuando se usa el AudioManagerenrutamiento dinámico, según el ID de zona. y el ID del grupo de volúmenes Obtenga el grupo de volúmenes y configure el volumen a través del grupo de volúmenes.

En CarVolumeGroup, primero obtenga el volumen en forma de ganancia del volumen descrito por índice, configure el volumen para cada dispositivo en el grupo de volúmenes y, finalmente, actualice el volumen actual y guarde el valor del volumen en forma de índice:

    void setCurrentGainIndex(int gainIndex) {
        Preconditions.checkArgument(isValidGainIndex(gainIndex),
                "Gain out of range (%d:%d) index %d", mMinGain, mMaxGain, gainIndex);
        synchronized (mLock) {
            if (mIsMuted) {
                setMuteLocked(false);
            }
            setCurrentGainIndexLocked(gainIndex);
        }
    }

    private void setCurrentGainIndexLocked(int gainIndex) {
        int gainInMillibels = getGainForIndex(gainIndex);
        for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
            CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
            info.setCurrentGain(gainInMillibels);
        }

        mCurrentGainIndex = gainIndex;

        storeGainIndexForUserLocked(mCurrentGainIndex, mUserId);
    }
 . . . . . .
    @GuardedBy("mLock")
    private void storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId) {
        mSettingsManager.storeVolumeGainIndexForUser(userId,
                mZoneId, mId, gainIndex);
    }

    private int getGainForIndex(int gainIndex) {
        return mMinGain + gainIndex * mStepSize;
    }

En CarAudioDeviceInfo( ) configure el volumen del dispositivo packages/services/Car/service/src/com/android/car/audio/CarAudioDeviceInfo.javaa través de :AudioManager

    // Input is in millibels
    void setCurrentGain(int gainInMillibels) {
        // Clamp the incoming value to our valid range.  Out of range values ARE legal input
        if (gainInMillibels < mMinGain) {
            gainInMillibels = mMinGain;
        } else if (gainInMillibels > mMaxGain) {
            gainInMillibels = mMaxGain;
        }

        // Push the new gain value down to our underlying port which will cause it to show up
        // at the HAL.
        AudioGain audioGain = getAudioGain();
        if (audioGain == null) {
            Slog.e(CarLog.TAG_AUDIO, "getAudioGain() returned null.");
            return;
        }

        // size of gain values is 1 in MODE_JOINT
        AudioGainConfig audioGainConfig = audioGain.buildConfig(
                AudioGain.MODE_JOINT,
                audioGain.channelMask(),
                new int[] { gainInMillibels },
                0);
        if (audioGainConfig == null) {
            Slog.e(CarLog.TAG_AUDIO, "Failed to construct AudioGainConfig");
            return;
        }

        int r = AudioManager.setAudioPortGain(getAudioDevicePort(), audioGainConfig);
        if (r == AudioManager.SUCCESS) {
            // Since we can't query for the gain on a device port later,
            // we have to remember what we asked for
            mCurrentGain = gainInMillibels;
        } else {
            Slog.e(CarLog.TAG_AUDIO, "Failed to setAudioPortGain: " + r);
        }
    }

El proceso exclusivo de ajuste del volumen del automóvil en AAOS probablemente sea así.

Hecho.

Supongo que te gusta

Origin blog.csdn.net/tq08g2z/article/details/129329989
Recomendado
Clasificación