1/*
2 * Copyright (C) 2016 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
17package com.android.car;
18
19import android.car.settings.CarSettings;
20import android.hardware.automotive.vehicle.V2_0.VehicleAudioContextFlag;
21import android.media.AudioAttributes;
22import android.media.AudioManager;
23import android.util.Log;
24import android.util.SparseArray;
25
26import java.util.Arrays;
27
28public class VolumeUtils {
29    private static final String TAG = "VolumeUtils";
30
31    public static final int[] LOGICAL_STREAMS = {
32            AudioManager.STREAM_VOICE_CALL,
33            AudioManager.STREAM_SYSTEM,
34            AudioManager.STREAM_RING,
35            AudioManager.STREAM_MUSIC,
36            AudioManager.STREAM_ALARM,
37            AudioManager.STREAM_NOTIFICATION,
38            AudioManager.STREAM_DTMF,
39    };
40
41    public static final int[] CAR_AUDIO_CONTEXT = {
42            VehicleAudioContextFlag.MUSIC_FLAG,
43            VehicleAudioContextFlag.NAVIGATION_FLAG,
44            VehicleAudioContextFlag.VOICE_COMMAND_FLAG,
45            VehicleAudioContextFlag.CALL_FLAG,
46            VehicleAudioContextFlag.RINGTONE_FLAG,
47            VehicleAudioContextFlag.ALARM_FLAG,
48            VehicleAudioContextFlag.NOTIFICATION_FLAG,
49            VehicleAudioContextFlag.UNKNOWN_FLAG,
50            VehicleAudioContextFlag.SAFETY_ALERT_FLAG,
51            VehicleAudioContextFlag.CD_ROM_FLAG,
52            VehicleAudioContextFlag.AUX_AUDIO_FLAG,
53            VehicleAudioContextFlag.SYSTEM_SOUND_FLAG,
54            VehicleAudioContextFlag.RADIO_FLAG
55    };
56
57    public static final SparseArray<String> CAR_AUDIO_CONTEXT_SETTINGS = new SparseArray<>();
58    static {
59        CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.UNKNOWN_FLAG,
60                CarSettings.Global.KEY_VOLUME_MUSIC);
61        CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.MUSIC_FLAG,
62                CarSettings.Global.KEY_VOLUME_MUSIC);
63        CAR_AUDIO_CONTEXT_SETTINGS.put(
64                VehicleAudioContextFlag.NAVIGATION_FLAG,
65                CarSettings.Global.KEY_VOLUME_NAVIGATION);
66        CAR_AUDIO_CONTEXT_SETTINGS.put(
67                VehicleAudioContextFlag.VOICE_COMMAND_FLAG,
68                CarSettings.Global.KEY_VOLUME_VOICE_COMMAND);
69        CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.CALL_FLAG,
70                CarSettings.Global.KEY_VOLUME_CALL);
71        CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.RINGTONE_FLAG,
72                CarSettings.Global.KEY_VOLUME_RINGTONE);
73        CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.ALARM_FLAG,
74                CarSettings.Global.KEY_VOLUME_ALARM);
75        CAR_AUDIO_CONTEXT_SETTINGS.put(
76                VehicleAudioContextFlag.NOTIFICATION_FLAG,
77                CarSettings.Global.KEY_VOLUME_NOTIFICATION);
78        CAR_AUDIO_CONTEXT_SETTINGS.put(
79                VehicleAudioContextFlag.SAFETY_ALERT_FLAG,
80                CarSettings.Global.KEY_VOLUME_SAFETY_ALERT);
81        CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.CD_ROM_FLAG,
82                CarSettings.Global.KEY_VOLUME_CD_ROM);
83        CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.AUX_AUDIO_FLAG,
84                CarSettings.Global.KEY_VOLUME_AUX);
85        CAR_AUDIO_CONTEXT_SETTINGS.put(
86                VehicleAudioContextFlag.SYSTEM_SOUND_FLAG,
87                CarSettings.Global.KEY_VOLUME_SYSTEM_SOUND);
88        CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.RADIO_FLAG,
89                CarSettings.Global.KEY_VOLUME_RADIO);
90    }
91
92    public static String streamToName(int stream) {
93        switch (stream) {
94            case AudioManager.STREAM_ALARM: return "Alarm";
95            case AudioManager.STREAM_MUSIC: return "Music";
96            case AudioManager.STREAM_NOTIFICATION: return "Notification";
97            case AudioManager.STREAM_RING: return "Ring";
98            case AudioManager.STREAM_VOICE_CALL: return "Call";
99            case AudioManager.STREAM_SYSTEM: return "System";
100            case AudioManager.STREAM_DTMF: return "DTMF";
101            default: return "Unknown";
102        }
103    }
104
105    public static int androidStreamToCarContext(int logicalAndroidStream) {
106        switch (logicalAndroidStream) {
107            case AudioManager.STREAM_VOICE_CALL:
108                return VehicleAudioContextFlag.CALL_FLAG;
109            case AudioManager.STREAM_SYSTEM:
110                return VehicleAudioContextFlag.SYSTEM_SOUND_FLAG;
111            case AudioManager.STREAM_RING:
112                return VehicleAudioContextFlag.RINGTONE_FLAG;
113            case AudioManager.STREAM_MUSIC:
114                return VehicleAudioContextFlag.MUSIC_FLAG;
115            case AudioManager.STREAM_ALARM:
116                return VehicleAudioContextFlag.ALARM_FLAG;
117            case AudioManager.STREAM_NOTIFICATION:
118                return VehicleAudioContextFlag.NOTIFICATION_FLAG;
119            case AudioManager.STREAM_DTMF:
120                return VehicleAudioContextFlag.SYSTEM_SOUND_FLAG;
121            default:
122                return VehicleAudioContextFlag.UNKNOWN_FLAG;
123        }
124    }
125
126    public static int carContextToAndroidStream(int carContext) {
127        switch (carContext) {
128            case VehicleAudioContextFlag.CALL_FLAG:
129                return AudioManager.STREAM_VOICE_CALL;
130            case VehicleAudioContextFlag.RINGTONE_FLAG:
131                return AudioManager.STREAM_RING;
132            case VehicleAudioContextFlag.SYSTEM_SOUND_FLAG:
133                return AudioManager.STREAM_SYSTEM;
134            case VehicleAudioContextFlag.NOTIFICATION_FLAG:
135                return AudioManager.STREAM_NOTIFICATION;
136            case VehicleAudioContextFlag.MUSIC_FLAG:
137                return AudioManager.STREAM_MUSIC;
138            case VehicleAudioContextFlag.ALARM_FLAG:
139                return AudioManager.STREAM_ALARM;
140            default:
141                return AudioManager.STREAM_MUSIC;
142        }
143    }
144
145    public static int androidStreamToCarUsage(int logicalAndroidStream) {
146        return CarAudioAttributesUtil.getCarUsageFromAudioAttributes(
147                new AudioAttributes.Builder()
148                        .setLegacyStreamType(logicalAndroidStream).build());
149    }
150
151    private final SparseArray<Float[]> mStreamAmplLookup = new SparseArray<>(7);
152
153    private static final float LN_10 = 2.302585093f;
154    // From cs/#android/frameworks/av/media/libmedia/AudioSystem.cpp
155    private static final float DB_PER_STEP = -.5f;
156
157    private final AudioManager mAudioManager;
158
159    public VolumeUtils(AudioManager audioManager) {
160        mAudioManager = audioManager;
161        for(int i : LOGICAL_STREAMS) {
162            initStreamLookup(i);
163        }
164    }
165
166    private void initStreamLookup(int streamType) {
167        int maxIndex = mAudioManager.getStreamMaxVolume(streamType);
168        Float[] amplList = new Float[maxIndex + 1];
169
170        for (int i = 0; i <= maxIndex; i++) {
171            amplList[i] = volIndexToAmpl(i, maxIndex);
172        }
173        Log.d(TAG, streamToName(streamType) + ": " + Arrays.toString(amplList));
174        mStreamAmplLookup.put(streamType, amplList);
175    }
176
177
178    public static int closestIndex(float desired, Float[] list) {
179        float min = Float.MAX_VALUE;
180        int closestIndex = 0;
181
182        for (int i = 0; i < list.length; i++) {
183            float diff = Math.abs(list[i] - desired);
184            if (diff < min) {
185                min = diff;
186                closestIndex = i;
187            }
188        }
189        return closestIndex;
190    }
191
192    public void adjustStreamVol(int stream, int desired, int actual, int maxIndex) {
193        float gain = getTrackGain(desired, actual, maxIndex);
194        int index = closestIndex(gain, mStreamAmplLookup.get(stream));
195        if (index == mAudioManager.getStreamVolume(stream)) {
196            return;
197        } else {
198            mAudioManager.setStreamVolume(stream, index, 0 /*don't show UI*/);
199        }
200    }
201
202    /**
203     * Returns the gain which, when applied to an a stream with volume
204     * actualVolIndex, will make the output volume equivalent to a stream with a gain of
205     * 1.0 playing on a stream with volume desiredVolIndex.
206     *
207     * Computing this is non-trivial because the gain is applied on a linear scale while the volume
208     * indices map to a log (dB) scale.
209     *
210     * The computation is copied from cs/#android/frameworks/av/media/libmedia/AudioSystem.cpp
211     */
212    float getTrackGain(int desiredVolIndex, int actualVolIndex, int maxIndex) {
213        if (desiredVolIndex == actualVolIndex) {
214            return 1.0f;
215        }
216        return volIndexToAmpl(desiredVolIndex, maxIndex)
217                / volIndexToAmpl(actualVolIndex, maxIndex);
218    }
219
220    /**
221     * Returns the amplitude corresponding to volIndex. Guaranteed to return a non-negative value.
222     */
223    private float volIndexToAmpl(int volIndex, int maxIndex) {
224        // Normalize volIndex to be in the range [0, 100].
225        int volume = (int) ((float) volIndex / maxIndex * 100.0f);
226        return logToLinear(volumeToDecibels(volume));
227    }
228
229    /**
230     * volume is in the range [0, 100].
231     */
232    private static float volumeToDecibels(int volume) {
233        return (100 - volume) * DB_PER_STEP;
234    }
235
236    /**
237     * Corresponds to the function linearToLog in AudioSystem.cpp.
238     */
239    private static float logToLinear(float decibels) {
240        return decibels < 0.0f ? (float) Math.exp(decibels * LN_10 / 20.0f) : 1.0f;
241    }
242}
243