1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.audio.hal;
18 
19 import static android.media.audio.common.AudioDeviceDescription.CONNECTION_BUS;
20 import static android.media.audio.common.AudioDeviceType.IN_DEVICE;
21 import static android.media.audio.common.AudioDeviceType.OUT_DEVICE;
22 import static android.media.audio.common.AudioGainMode.JOINT;
23 
24 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
25 
26 import android.annotation.NonNull;
27 import android.media.audio.common.AudioDevice;
28 import android.media.audio.common.AudioGain;
29 import android.media.audio.common.AudioPort;
30 
31 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
32 import com.android.internal.util.Preconditions;
33 
34 import java.util.Objects;
35 
36 /**
37  * Audio Device info received from HAL as part of dynamic gain stage configration
38  */
39 public final class HalAudioDeviceInfo {
40     private final int mId;
41     private final String mName;
42     private final AudioGain mAudioGain;
43     private final int mType;
44     private final String mConnection;
45     private final String mAddress;
46     private static final int AUDIO_PORT_EXT_DEVICE = 1;
47 
HalAudioDeviceInfo(AudioPort port)48     public HalAudioDeviceInfo(AudioPort port) {
49         Objects.requireNonNull(port, "Audio port can not be null");
50 
51         Preconditions.checkArgument(port.ext.getTag() == AUDIO_PORT_EXT_DEVICE,
52                 "Invalid audio port ext setting: %d", port.ext.getTag());
53         AudioDevice device = Objects.requireNonNull(port.ext.getDevice().device,
54                 "Audio device can not be null");
55         checkIfAudioDeviceIsValidOutputBus(device);
56 
57         mId = port.id;
58         mName = port.name;
59         mAudioGain = getAudioGain(port.gains);
60         mType = device.type.type;
61         mConnection = device.type.connection;
62         mAddress = device.address.getId();
63     }
64 
getId()65     public int getId() {
66         return mId;
67     }
68 
getName()69     public String getName() {
70         return mName;
71     }
72 
getGainMinValue()73     public int getGainMinValue() {
74         return mAudioGain.minValue;
75     }
76 
getGainMaxValue()77     public int getGainMaxValue() {
78         return mAudioGain.maxValue;
79     }
80 
getGainDefaultValue()81     public int getGainDefaultValue() {
82         return mAudioGain.defaultValue;
83     }
84 
getGainStepValue()85     public int getGainStepValue() {
86         return mAudioGain.stepValue;
87     }
88 
getType()89     public int getType() {
90         return mType;
91     }
92 
getConnection()93     public String getConnection() {
94         return mConnection;
95     }
96 
getAddress()97     public String getAddress() {
98         return mAddress;
99     }
100 
isOutputDevice()101     public boolean isOutputDevice() {
102         return mType == OUT_DEVICE;
103     }
104 
isInputDevice()105     public boolean isInputDevice() {
106         return mType == IN_DEVICE;
107     }
108 
109     @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
110     @Override
toString()111     public String toString() {
112         return new StringBuilder()
113                 .append("{mId: ").append(mId).append(", mName: ").append(mName)
114                 .append(", mAudioGain: ").append(Objects.toString(mAudioGain))
115                 .append(", mType: ").append(mType).append(", mConnection: ").append(mConnection)
116                 .append(", mAddress: ").append(mAddress).append("}").toString();
117     }
118 
119     @Override
equals(Object o)120     public boolean equals(Object o) {
121         if (this == o) {
122             return true;
123         }
124 
125         if (!(o instanceof HalAudioDeviceInfo)) {
126             return false;
127         }
128 
129         HalAudioDeviceInfo rhs = (HalAudioDeviceInfo) o;
130 
131         // mId is not reliable until Audio HAL migrates to AIDL
132         return mType == rhs.mType && mName.equals(rhs.mName) && mConnection.equals(rhs.mConnection)
133                 && mAddress.equals(rhs.mAddress) && Objects.equals(mAudioGain, rhs.mAudioGain);
134     }
135 
136     @Override
hashCode()137     public int hashCode() {
138         // mId is not reliable until Audio HAL migrates to AIDL
139         return Objects.hash(mName, mAudioGain, mType, mConnection, mAddress);
140     }
141 
checkIfAudioDeviceIsValidOutputBus(AudioDevice device)142     private void checkIfAudioDeviceIsValidOutputBus(AudioDevice device) {
143         Preconditions.checkArgument((device.type.type == OUT_DEVICE)
144                         || (device.type.type == IN_DEVICE),
145                 "Invalid audio device type (expecting IN/OUT_DEVICE): %d", device.type.type);
146 
147         Preconditions.checkArgument(device.type.connection.equals(CONNECTION_BUS),
148                 "Invalid audio device connection (expecting CONNECTION_BUS): %s",
149                 device.type.connection);
150 
151         Preconditions.checkStringNotEmpty(device.address.getId(),
152                 "Audio device address cannot be empty");
153     }
154 
getAudioGain(@onNull AudioGain[] gains)155     private static AudioGain getAudioGain(@NonNull AudioGain[] gains) {
156         Objects.requireNonNull(gains, "Audio gains can not be null");
157         Preconditions.checkArgument(gains.length > 0, "Audio port must have gains defined");
158         for (int index = 0; index < gains.length; index++) {
159             AudioGain gain = Objects.requireNonNull(gains[index], "Audio gain can not be null");
160             if (gain.mode == JOINT) {
161                 return checkAudioGainConfiguration(gain);
162             }
163         }
164         throw new IllegalStateException("Audio port does not have a valid audio gain");
165     }
166 
checkAudioGainConfiguration(AudioGain gain)167     private static AudioGain checkAudioGainConfiguration(AudioGain gain) {
168         Preconditions.checkArgument(gain.maxValue >= gain.minValue,
169                 "Max gain %d is lower than min gain %d",
170                 gain.maxValue, gain.minValue);
171         Preconditions.checkArgument((gain.defaultValue >= gain.minValue)
172                         && (gain.defaultValue <= gain.maxValue),
173                 "Default gain %d not in range (%d,%d)", gain.defaultValue,
174                 gain.minValue, gain.maxValue);
175         Preconditions.checkArgument(gain.stepValue > 0,
176                 "Gain step value must be greater than zero: %d", gain.stepValue);
177         Preconditions.checkArgument(
178                 ((gain.maxValue - gain.minValue) % gain.stepValue) == 0,
179                 "Gain step value %d greater than min gain to max gain range %d",
180                 gain.stepValue, gain.maxValue - gain.minValue);
181         Preconditions.checkArgument(
182                 ((gain.defaultValue - gain.minValue) % gain.stepValue) == 0,
183                 "Gain step value %d greater than min gain to default gain range %d",
184                 gain.stepValue, gain.defaultValue - gain.minValue);
185         return gain;
186     }
187 }
188