Merge changes from topic "per_user_volume_settings" into rvc-dev
* changes:
Update Volume Embedded Kitchen Sink Fragment.
Fixed car audio volume group settings for user.
diff --git a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java b/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
index 8dc2626..e5c5740 100644
--- a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
+++ b/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
@@ -28,6 +28,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.os.UserManager;
@@ -48,6 +49,7 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.quality.Strictness;
import org.mockito.session.MockitoSessionBuilder;
+import org.mockito.stubbing.Answer;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -113,7 +115,7 @@
* Adds key-value(int) pair in mocked Settings.Global and Settings.Secure
*/
protected void putSettingsInt(@NonNull String key, int value) {
- mSettings.insertInt(key, value);
+ mSettings.insertObject(key, value);
}
/**
@@ -127,7 +129,7 @@
* Adds key-value(String) pair in mocked Settings.Global and Settings.Secure
*/
protected void putSettingsString(@NonNull String key, @NonNull String value) {
- mSettings.insertString(key, value);
+ mSettings.insertObject(key, value);
}
/**
@@ -234,6 +236,7 @@
StaticMockitoSessionBuilder builder = mockitoSession()
.strictness(getSessionStrictness())
.mockStatic(Settings.Global.class)
+ .mockStatic(Settings.System.class)
.mockStatic(Settings.Secure.class);
CustomMockitoSessionBuilder customBuilder =
@@ -313,70 +316,92 @@
}
// TODO (b/155523104): Add log
+ // TODO (b/156033195): Clean settings API
private static final class MockSettings {
- private HashMap<String, Integer> mIntMapping = new HashMap<String, Integer>();
- private HashMap<String, String> mStringMapping = new HashMap<String, String>();
+ private static final int INVALID_DEFAULT_INDEX = -1;
+ private HashMap<String, Object> mSettingsMapping = new HashMap<>();
MockSettings() {
- when(Settings.Global.putInt(any(), any(), anyInt())).thenAnswer(invocation -> {
- String key = (String) invocation.getArguments()[1];
- int value = (int) invocation.getArguments()[2];
- insertInt(key, value);
- return null;
- });
- when(Settings.Global.getInt(any(), any(), anyInt())).thenAnswer(invocation -> {
- String key = (String) invocation.getArguments()[1];
- int defaultValue = (int) invocation.getArguments()[2];
- return getInt(key, defaultValue);
- });
+ Answer<Object> insertObjectAnswer =
+ invocation -> insertObjectFromInvocation(invocation, 1, 2);
+ Answer<Integer> getIntAnswer = invocation ->
+ getAnswer(invocation, Integer.class, 1, 2);
+ Answer<String> getStringAnswer = invocation ->
+ getAnswer(invocation, String.class, 1, INVALID_DEFAULT_INDEX);
+
+
+ when(Settings.Global.putInt(any(), any(), anyInt())).thenAnswer(insertObjectAnswer);
+
+ when(Settings.Global.getInt(any(), any(), anyInt())).thenAnswer(getIntAnswer);
when(Settings.Secure.putIntForUser(any(), any(), anyInt(), anyInt()))
- .thenAnswer(invocation -> {
- String key = (String) invocation.getArguments()[1];
- int value = (int) invocation.getArguments()[2];
- insertInt(key, value);
- return null;
- });
+ .thenAnswer(insertObjectAnswer);
when(Settings.Secure.getIntForUser(any(), any(), anyInt(), anyInt()))
- .thenAnswer(invocation -> {
- String key = (String) invocation.getArguments()[1];
- int defaultValue = (int) invocation.getArguments()[2];
- return getInt(key, defaultValue);
- });
+ .thenAnswer(getIntAnswer);
- when(Settings.Global.putString(any(), any(), any())).thenAnswer(invocation -> {
- String key = (String) invocation.getArguments()[1];
- String value = (String) invocation.getArguments()[2];
- insertString(key, value);
- return null;
- });
+ when(Settings.Global.putString(any(), any(), any()))
+ .thenAnswer(insertObjectAnswer);
- when(Settings.Global.getString(any(), any())).thenAnswer(invocation -> {
- String key = (String) invocation.getArguments()[1];
- return getString(key);
- });
+ when(Settings.Global.getString(any(), any())).thenAnswer(getStringAnswer);
+
+ when(Settings.System.putIntForUser(any(), any(), anyInt(), anyInt()))
+ .thenAnswer(insertObjectAnswer);
+
+ when(Settings.System.getIntForUser(any(), any(), anyInt(), anyInt()))
+ .thenAnswer(getIntAnswer);
}
- public void insertInt(String key, int value) {
- mIntMapping.put(key, value);
+ private Object insertObjectFromInvocation(InvocationOnMock invocation,
+ int keyIndex, int valueIndex) {
+ String key = (String) invocation.getArguments()[keyIndex];
+ Object value = invocation.getArguments()[valueIndex];
+ insertObject(key, value);
+ return null;
+ }
+
+ private void insertObject(String key, Object value) {
+ if (VERBOSE) Log.v(TAG, "Inserting Setting " + key + ": " + value);
+ mSettingsMapping.put(key, value);
+ }
+
+ private <T> T getAnswer(InvocationOnMock invocation, Class<T> clazz,
+ int keyIndex, int defaultValueIndex) {
+ String key = (String) invocation.getArguments()[keyIndex];
+ T defaultValue = null;
+ if (defaultValueIndex > INVALID_DEFAULT_INDEX) {
+ defaultValue = safeCast(invocation.getArguments()[defaultValueIndex], clazz);
+ }
+ return get(key, defaultValue, clazz);
+ }
+
+ @Nullable
+ private <T> T get(String key, T defaultValue, Class<T> clazz) {
+ if (VERBOSE) Log.v(TAG, "Getting Setting " + key);
+ Object value = mSettingsMapping.get(key);
+ if (value == null) {
+ return defaultValue;
+ }
+ return safeCast(value, clazz);
+ }
+
+ private <T> T safeCast(Object value, Class<T> clazz) {
+ if (value == null) {
+ return null;
+ }
+ Preconditions.checkArgument(value.getClass() == clazz,
+ "Setting value has class %s but requires class %s",
+ value.getClass(), clazz);
+ return (T) value;
+ }
+
+ private String getString(String key) {
+ return get(key, null, String.class);
}
public int getInt(String key) {
- return mIntMapping.get(key);
- }
-
- public int getInt(String key, int defaultValue) {
- return mIntMapping.getOrDefault(key, defaultValue);
- }
-
- public void insertString(String key, String value) {
- mStringMapping.put(key, value);
- }
-
- public String getString(String key) {
- return mStringMapping.get(key);
+ return (int) get(key, null, Integer.class);
}
}
diff --git a/service/src/com/android/car/CarOccupantZoneService.java b/service/src/com/android/car/CarOccupantZoneService.java
index 545c636..d9949c0 100644
--- a/service/src/com/android/car/CarOccupantZoneService.java
+++ b/service/src/com/android/car/CarOccupantZoneService.java
@@ -551,6 +551,13 @@
}
/**
+ * returns the current driver user id.
+ */
+ public @UserIdInt int getDriverUserId() {
+ return getCurrentUser();
+ }
+
+ /**
* Sets the mapping for audio zone id to occupant zone id.
*
* @param audioZoneIdToOccupantZoneMapping map for audio zone id, where key is the audio zone id
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 6cf9cd8..c880709 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
import android.car.Car;
import android.car.CarOccupantZoneManager;
import android.car.CarOccupantZoneManager.OccupantZoneConfigChangeListener;
@@ -1151,33 +1150,39 @@
}
private void handleOccupantZoneUserChanged() {
+ int driverUserId = mOccupantZoneService.getDriverUserId();
synchronized (mImplLock) {
- if (isOccupantZoneMappingAvailable()) {
+ if (!isOccupantZoneMappingAvailable()) {
//No occupant zone to audio zone mapping, re-adjust to settings driver.
- int driverId = ActivityManager.getCurrentUser();
for (int index = 0; index < mCarAudioZones.length; index++) {
CarAudioZone zone = mCarAudioZones[index];
- zone.updateVolumeGroupsForUser(driverId);
- mFocusHandler.updateUserForZoneId(zone.getId(), driverId);
+ zone.updateVolumeGroupsForUser(driverUserId);
+ mFocusHandler.updateUserForZoneId(zone.getId(), driverUserId);
}
return;
}
for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId);
- updateUserForOccupantZoneLocked(occupantZoneId, audioZoneId);
+ updateUserForOccupantZoneLocked(occupantZoneId, audioZoneId, driverUserId);
}
}
}
private boolean isOccupantZoneMappingAvailable() {
- return mAudioZoneIdToOccupantZoneIdMapping.size() == 0;
+ return mAudioZoneIdToOccupantZoneIdMapping.size() > 0;
}
- private void updateUserForOccupantZoneLocked(int occupantZoneId, int audioZoneId) {
+ private void updateUserForOccupantZoneLocked(int occupantZoneId, int audioZoneId,
+ @UserIdInt int driverUserId) {
+ CarAudioZone zone = getAudioZoneForZoneIdLocked(audioZoneId);
int userId = mOccupantZoneService.getUserForOccupant(occupantZoneId);
int prevUserId = getUserIdForZoneLocked(audioZoneId);
+ Objects.requireNonNull(zone, () ->
+ "setUserIdDeviceAffinity for userId " + userId
+ + " in zone " + audioZoneId + " Failed, invalid zone.");
+
// user in occupant zone has not changed
if (userId == prevUserId) {
return;
@@ -1186,30 +1191,30 @@
// This would be true even if the new user is UserHandle.USER_NULL,
// as that indicates the user has logged out.
removeUserIdDeviceAffinitiesLocked(prevUserId);
- resetCarZonesAudioFocus(audioZoneId);
if (userId == UserHandle.USER_NULL) {
+ // Reset zone back to driver user id
+ resetZoneToDefaultUser(zone, driverUserId);
return;
}
- CarAudioZone zone = getAudioZoneForZoneIdLocked(audioZoneId);
- if (zone != null
- && !mAudioPolicy.setUserIdDeviceAffinity(userId, zone.getAudioDeviceInfos())) {
+ if (!mAudioPolicy.setUserIdDeviceAffinity(userId, zone.getAudioDeviceInfos())) {
throw new IllegalStateException(String.format(
"setUserIdDeviceAffinity for userId %d in zone %d Failed,"
+ " could not set audio routing.",
userId, audioZoneId));
- } else if (zone == null) {
- throw new IllegalStateException(String.format(
- "setUserIdDeviceAffinity for userId %d in zone %d Failed, invalid zone.",
- userId, audioZoneId));
}
mAudioZoneIdToUserIdMapping.put(audioZoneId, userId);
zone.updateVolumeGroupsForUser(userId);
mFocusHandler.updateUserForZoneId(audioZoneId, userId);
}
- private void resetCarZonesAudioFocus(int audioZoneId) {
- mFocusHandler.updateUserForZoneId(audioZoneId, UserHandle.USER_NULL);
+ private void resetZoneToDefaultUser(CarAudioZone zone, @UserIdInt int driverUserId) {
+ resetCarZonesAudioFocus(zone.getId(), driverUserId);
+ zone.updateVolumeGroupsForUser(driverUserId);
+ }
+
+ private void resetCarZonesAudioFocus(int audioZoneId, @UserIdInt int driverUserId) {
+ mFocusHandler.updateUserForZoneId(audioZoneId, driverUserId);
}
private CarAudioZone getAudioZoneForZoneIdLocked(int audioZoneId) {
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index 35d5a46..5244b67 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -17,10 +17,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
+import android.annotation.UserIdInt;
import android.car.media.CarAudioManager;
import android.content.Context;
import android.media.AudioDevicePort;
+import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
@@ -49,12 +50,15 @@
private final SparseArray<String> mContextToAddress = new SparseArray<>();
private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo = new HashMap<>();
+ private final Object mLock = new Object();
+
private int mDefaultGain = Integer.MIN_VALUE;
private int mMaxGain = Integer.MIN_VALUE;
private int mMinGain = Integer.MAX_VALUE;
private int mStepSize = 0;
private int mStoredGainIndex;
private int mCurrentGainIndex = -1;
+ private @UserIdInt int mUserId = UserHandle.USER_CURRENT;
/**
* Constructs a {@link CarVolumeGroup} instance
@@ -66,8 +70,7 @@
mSettingsManager = settings;
mZoneId = zoneId;
mId = id;
-
- updateUserId(ActivityManager.getCurrentUser());
+ mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, mId);
}
/**
@@ -152,28 +155,31 @@
CarAudioContext.toString(carAudioContext),
mContextToAddress.get(carAudioContext)));
- if (mAddressToCarAudioDeviceInfo.size() == 0) {
- mStepSize = info.getStepValue();
- } else {
- Preconditions.checkArgument(
- info.getStepValue() == mStepSize,
- "Gain controls within one group must have same step value");
- }
+ synchronized (mLock) {
+ if (mAddressToCarAudioDeviceInfo.size() == 0) {
+ 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());
+ 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.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();
+ }
+ updateCurrentGainIndexLocked();
}
- if (info.getMaxGain() > mMaxGain) {
- mMaxGain = info.getMaxGain();
- }
- if (info.getMinGain() < mMinGain) {
- mMinGain = info.getMinGain();
- }
- updateCurrentGainIndex();
}
/**
@@ -181,42 +187,60 @@
* @param userId new user
* @note also reloads the store gain index for the user
*/
- private void updateUserId(int userId) {
- mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(userId, mZoneId, mId);
- Log.i(CarLog.TAG_AUDIO, "updateUserId userId " + userId
- + " mStoredGainIndex " + mStoredGainIndex);
+ private void updateUserIdLocked(@UserIdInt int userId) {
+ mUserId = userId;
+ mStoredGainIndex = getCurrentGainIndexForUserLocked();
+ }
+
+ private int getCurrentGainIndexForUserLocked() {
+ int gainIndexForUser = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId,
+ mId);
+ Log.i(CarLog.TAG_AUDIO, "updateUserId userId " + mUserId
+ + " gainIndexForUser " + gainIndexForUser);
+ return gainIndexForUser;
}
/**
* Update the current gain index based on the stored gain index
*/
- private void updateCurrentGainIndex() {
- if (mStoredGainIndex < getMinGainIndex() || mStoredGainIndex > getMaxGainIndex()) {
- // We expected to load a value from last boot, but if we didn't (perhaps this is the
- // first boot ever?), then use the highest "default" we've seen to initialize
- // ourselves.
- mCurrentGainIndex = getIndexForGain(mDefaultGain);
- } else {
- // Just use the gain index we stored last time the gain was set (presumably during our
- // last boot cycle).
- mCurrentGainIndex = mStoredGainIndex;
+ private void updateCurrentGainIndexLocked() {
+ synchronized (mLock) {
+ if (mStoredGainIndex < getIndexForGainLocked(mMinGain)
+ || mStoredGainIndex > getIndexForGainLocked(mMaxGain)) {
+ // We expected to load a value from last boot, but if we didn't (perhaps this is the
+ // first boot ever?), then use the highest "default" we've seen to initialize
+ // ourselves.
+ mCurrentGainIndex = getIndexForGainLocked(mDefaultGain);
+ } else {
+ // Just use the gain index we stored last time the gain was
+ // set (presumably during our last boot cycle).
+ mCurrentGainIndex = mStoredGainIndex;
+ }
}
}
private int getDefaultGainIndex() {
- return getIndexForGain(mDefaultGain);
+ synchronized (mLock) {
+ return getIndexForGainLocked(mDefaultGain);
+ }
}
int getMaxGainIndex() {
- return getIndexForGain(mMaxGain);
+ synchronized (mLock) {
+ return getIndexForGainLocked(mMaxGain);
+ }
}
int getMinGainIndex() {
- return getIndexForGain(mMinGain);
+ synchronized (mLock) {
+ return getIndexForGainLocked(mMinGain);
+ }
}
int getCurrentGainIndex() {
- return mCurrentGainIndex;
+ synchronized (mLock) {
+ return mCurrentGainIndex;
+ }
}
/**
@@ -224,37 +248,43 @@
* @param gainIndex The gain index
*/
void setCurrentGainIndex(int gainIndex) {
- int gainInMillibels = getGainForIndex(gainIndex);
+ synchronized (mLock) {
+ int gainInMillibels = getGainForIndexLocked(gainIndex);
+ Preconditions.checkArgument(
+ gainInMillibels >= mMinGain && gainInMillibels <= mMaxGain,
+ "Gain out of range ("
+ + mMinGain + ":"
+ + mMaxGain + ") "
+ + gainInMillibels + "index "
+ + gainIndex);
- Preconditions.checkArgument(
- gainInMillibels >= mMinGain && gainInMillibels <= mMaxGain,
- "Gain out of range ("
- + mMinGain + ":"
- + mMaxGain + ") "
- + gainInMillibels + "index "
- + gainIndex);
+ for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
+ CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
+ info.setCurrentGain(gainInMillibels);
+ }
- for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
- CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
- info.setCurrentGain(gainInMillibels);
+ mCurrentGainIndex = gainIndex;
+
+ storeGainIndexForUserLocked(mCurrentGainIndex, mUserId);
}
+ }
- mCurrentGainIndex = gainIndex;
- mSettingsManager.storeVolumeGainIndexForUser(ActivityManager.getCurrentUser(),
+ private void storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId) {
+ mSettingsManager.storeVolumeGainIndexForUser(userId,
mZoneId, mId, gainIndex);
}
// Given a group level gain index, return the computed gain in millibells
// TODO (randolphs) If we ever want to add index to gain curves other than lock-stepped
// linear, this would be the place to do it.
- private int getGainForIndex(int gainIndex) {
+ private int getGainForIndexLocked(int gainIndex) {
return mMinGain + gainIndex * mStepSize;
}
// TODO (randolphs) if we ever went to a non-linear index to gain curve mapping, we'd need to
// revisit this as it assumes (at the least) that getGainForIndex is reversible. Luckily,
// this is an internal implementation details we could factor out if/when necessary.
- private int getIndexForGain(int gainInMillibel) {
+ private int getIndexForGainLocked(int gainInMillibel) {
return (gainInMillibel - mMinGain) / mStepSize;
}
@@ -281,37 +311,41 @@
/** Writes to dumpsys output */
void dump(String indent, PrintWriter writer) {
- writer.printf("%sCarVolumeGroup(%d)\n", indent, mId);
- writer.printf("%sUserId(%d)\n", indent, ActivityManager.getCurrentUser());
- writer.printf("%sGain values (min / max / default/ current): %d %d %d %d\n",
- indent, mMinGain, mMaxGain,
- mDefaultGain, getGainForIndex(mCurrentGainIndex));
- writer.printf("%sGain indexes (min / max / default / current): %d %d %d %d\n",
- indent, getMinGainIndex(), getMaxGainIndex(),
- getDefaultGainIndex(), mCurrentGainIndex);
- for (int i = 0; i < mContextToAddress.size(); i++) {
- writer.printf("%sContext: %s -> Address: %s\n", indent,
- CarAudioContext.toString(mContextToAddress.keyAt(i)),
- mContextToAddress.valueAt(i));
- }
- mAddressToCarAudioDeviceInfo.keySet().stream()
- .map(mAddressToCarAudioDeviceInfo::get)
- .forEach((info -> info.dump(indent, writer)));
+ synchronized (mLock) {
+ writer.printf("%sCarVolumeGroup(%d)\n", indent, mId);
+ writer.printf("%sUserId(%d)\n", indent, mUserId);
+ writer.printf("%sGain values (min / max / default/ current): %d %d %d %d\n",
+ indent, mMinGain, mMaxGain,
+ mDefaultGain, getGainForIndexLocked(mCurrentGainIndex));
+ writer.printf("%sGain indexes (min / max / default / current): %d %d %d %d\n",
+ indent, getMinGainIndex(), getMaxGainIndex(),
+ getDefaultGainIndex(), mCurrentGainIndex);
+ for (int i = 0; i < mContextToAddress.size(); i++) {
+ writer.printf("%sContext: %s -> Address: %s\n", indent,
+ CarAudioContext.toString(mContextToAddress.keyAt(i)),
+ mContextToAddress.valueAt(i));
+ }
+ mAddressToCarAudioDeviceInfo.keySet().stream()
+ .map(mAddressToCarAudioDeviceInfo::get)
+ .forEach((info -> info.dump(indent, writer)));
- // Empty line for comfortable reading
- writer.println();
+ // Empty line for comfortable reading
+ writer.println();
+ }
}
/**
* Load volumes for new user
* @param userId new user to load
*/
- void loadVolumesForUser(int userId) {
- //Update the volume for the new user
- updateUserId(userId);
- //Update the current gain index
- updateCurrentGainIndex();
- //Reset devices with current gain index
+ void loadVolumesForUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ //Update the volume for the new user
+ updateUserIdLocked(userId);
+ //Update the current gain index
+ updateCurrentGainIndexLocked();
+ //Reset devices with current gain index
+ }
setCurrentGainIndex(getCurrentGainIndex());
}
}
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/volume_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/volume_test.xml
index db48680..7cd5c95 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/volume_test.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/volume_test.xml
@@ -16,12 +16,25 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
- <ListView
- android:id="@+id/volume_list"
+
+ <LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
+ android:layout_marginLeft="20dp"
+ android:orientation="vertical"
android:layout_weight="3">
- </ListView>
+ <com.google.android.material.tabs.TabLayout
+ android:id="@+id/zones_tab"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content">
+ </com.google.android.material.tabs.TabLayout>
+ <androidx.viewpager.widget.ViewPager
+ android:id="@+id/zone_view_pager"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+ </androidx.viewpager.widget.ViewPager>
+ </LinearLayout>
<LinearLayout
android:layout_width="0dp"
@@ -31,12 +44,6 @@
android:layout_weight="1">
<Button
- android:id="@+id/refresh"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/refresh_volume"/>
-
- <Button
android:id="@+id/volume_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/zone_volume_tab.xml b/tests/EmbeddedKitchenSinkApp/res/layout/zone_volume_tab.xml
new file mode 100644
index 0000000..bb38d5a
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/zone_volume_tab.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ListView
+ android:id="@+id/volume_list"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3">
+ </ListView>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/AudioZoneVolumeTabAdapter.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/AudioZoneVolumeTabAdapter.java
new file mode 100644
index 0000000..3e94f28
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/AudioZoneVolumeTabAdapter.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.volume;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentStatePagerAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class AudioZoneVolumeTabAdapter extends FragmentStatePagerAdapter {
+
+ private final List<Fragment> mFragmentList = new ArrayList<>();
+ private final List<String> mFragmentTitleList = new ArrayList<>();
+ AudioZoneVolumeTabAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return mFragmentList.get(position);
+ }
+
+ public void addFragment(Fragment fragment, String title) {
+ mFragmentList.add(fragment);
+ mFragmentTitleList.add(title);
+ notifyDataSetChanged();
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mFragmentTitleList.get(position);
+ }
+
+ @Override
+ public int getCount() {
+ return mFragmentList.size();
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeAdapter.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeAdapter.java
similarity index 82%
rename from tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeAdapter.java
rename to tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeAdapter.java
index ec071dc..eb0c111 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeAdapter.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeAdapter.java
@@ -25,22 +25,21 @@
import android.widget.TextView;
import com.google.android.car.kitchensink.R;
-import com.google.android.car.kitchensink.volume.VolumeTestFragment.VolumeInfo;
+import com.google.android.car.kitchensink.volume.VolumeTestFragment.CarAudioZoneVolumeInfo;
-
-public class VolumeAdapter extends ArrayAdapter<VolumeInfo> {
+public final class CarAudioZoneVolumeAdapter extends ArrayAdapter<CarAudioZoneVolumeInfo> {
private final Context mContext;
- private VolumeInfo[] mVolumeList;
+ private CarAudioZoneVolumeInfo[] mVolumeList;
private final int mLayoutResourceId;
- private VolumeTestFragment mFragment;
+ private CarAudioZoneVolumeFragment mFragment;
-
- public VolumeAdapter(Context c, int layoutResourceId, VolumeInfo[] volumeList,
- VolumeTestFragment fragment) {
- super(c, layoutResourceId, volumeList);
+ public CarAudioZoneVolumeAdapter(Context context,
+ int layoutResourceId, CarAudioZoneVolumeInfo[] volumeList,
+ CarAudioZoneVolumeFragment fragment) {
+ super(context, layoutResourceId, volumeList);
mFragment = fragment;
- mContext = c;
+ mContext = context;
this.mLayoutResourceId = layoutResourceId;
this.mVolumeList = volumeList;
}
@@ -95,18 +94,17 @@
return mVolumeList.length;
}
-
- public void refreshVolumes(VolumeInfo[] volumes) {
+ public void refreshVolumes(CarAudioZoneVolumeInfo[] volumes) {
mVolumeList = volumes;
notifyDataSetChanged();
}
- static class ViewHolder {
- TextView id;
- TextView maxVolume;
- TextView currentVolume;
- Button upButton;
- Button downButton;
- Button requestButton;
+ private static final class ViewHolder {
+ public TextView id;
+ public TextView maxVolume;
+ public TextView currentVolume;
+ public Button upButton;
+ public Button downButton;
+ public Button requestButton;
}
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeFragment.java
new file mode 100644
index 0000000..c1a712d
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeFragment.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.volume;
+
+import android.car.media.CarAudioManager;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.R;
+import com.google.android.car.kitchensink.volume.VolumeTestFragment.CarAudioZoneVolumeInfo;
+
+public final class CarAudioZoneVolumeFragment extends Fragment {
+ private static final String TAG = "CarVolumeTest."
+ + CarAudioZoneVolumeFragment.class.getSimpleName();
+ private static final boolean DEBUG = true;
+
+ private static final int MSG_VOLUME_CHANGED = 0;
+ private static final int MSG_REQUEST_FOCUS = 1;
+ private static final int MSG_FOCUS_CHANGED = 2;
+
+ private final int mZoneId;
+ private final CarAudioManager mCarAudioManager;
+ private final AudioManager mAudioManager;
+ private CarAudioZoneVolumeInfo[] mVolumeInfos =
+ new CarAudioZoneVolumeInfo[0];
+ private final Handler mHandler = new VolumeHandler();
+
+ private CarAudioZoneVolumeAdapter mCarAudioZoneVolumeAdapter;
+ private final SparseIntArray mGroupIdIndexMap = new SparseIntArray();
+
+ public void sendChangeMessage() {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_VOLUME_CHANGED));
+ }
+
+ private class VolumeHandler extends Handler {
+ private AudioFocusListener mFocusListener;
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (DEBUG) {
+ Log.d(TAG, "zone " + mZoneId + " handleMessage : " + getMessageName(msg));
+ }
+ switch (msg.what) {
+ case MSG_VOLUME_CHANGED:
+ initVolumeInfo();
+ break;
+ case MSG_REQUEST_FOCUS:
+ int groupId = msg.arg1;
+ if (mFocusListener != null) {
+ mAudioManager.abandonAudioFocus(mFocusListener);
+ mVolumeInfos[mGroupIdIndexMap.get(groupId)].mHasFocus = false;
+ mCarAudioZoneVolumeAdapter.notifyDataSetChanged();
+ }
+
+ mFocusListener = new AudioFocusListener(groupId);
+ mAudioManager.requestAudioFocus(mFocusListener, groupId,
+ AudioManager.AUDIOFOCUS_GAIN);
+ break;
+ case MSG_FOCUS_CHANGED:
+ int focusGroupId = msg.arg1;
+ mVolumeInfos[mGroupIdIndexMap.get(focusGroupId)].mHasFocus = true;
+ mCarAudioZoneVolumeAdapter.refreshVolumes(mVolumeInfos);
+ break;
+ default :
+ Log.wtf(TAG,"VolumeHandler handleMessage called with unknown message"
+ + msg.what);
+
+ }
+ }
+ }
+
+ public CarAudioZoneVolumeFragment(int zoneId, CarAudioManager carAudioManager,
+ AudioManager audioManager) {
+ mZoneId = zoneId;
+ mCarAudioManager = carAudioManager;
+ mAudioManager = audioManager;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ if (DEBUG) {
+ Log.d(TAG, "onCreateView " + mZoneId);
+ }
+ View v = inflater.inflate(R.layout.zone_volume_tab, container, false);
+ ListView volumeListView = v.findViewById(R.id.volume_list);
+ mCarAudioZoneVolumeAdapter =
+ new CarAudioZoneVolumeAdapter(getContext(), R.layout.volume_item, mVolumeInfos,
+ this);
+ initVolumeInfo();
+ volumeListView.setAdapter(mCarAudioZoneVolumeAdapter);
+ return v;
+ }
+
+ void initVolumeInfo() {
+ int volumeGroupCount = mCarAudioManager.getVolumeGroupCount(mZoneId);
+ mVolumeInfos = new CarAudioZoneVolumeInfo[volumeGroupCount + 1];
+ mGroupIdIndexMap.clear();
+ CarAudioZoneVolumeInfo titlesInfo = new CarAudioZoneVolumeInfo();
+ titlesInfo.mId = "Group id";
+ titlesInfo.mCurrent = "Current";
+ titlesInfo.mMax = "Max";
+ mVolumeInfos[0] = titlesInfo;
+
+ int i = 1;
+ for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
+ CarAudioZoneVolumeInfo volumeInfo = new CarAudioZoneVolumeInfo();
+ mGroupIdIndexMap.put(groupId, i);
+ volumeInfo.mGroupId = groupId;
+ volumeInfo.mId = String.valueOf(groupId);
+ int current = mCarAudioManager.getGroupVolume(mZoneId, groupId);
+ int max = mCarAudioManager.getGroupMaxVolume(mZoneId, groupId);
+ volumeInfo.mCurrent = String.valueOf(current);
+ volumeInfo.mMax = String.valueOf(max);
+
+ mVolumeInfos[i] = volumeInfo;
+ if (DEBUG)
+ {
+ Log.d(TAG, groupId + " max: " + volumeInfo.mMax + " current: "
+ + volumeInfo.mCurrent);
+ }
+ i++;
+ }
+ mCarAudioZoneVolumeAdapter.refreshVolumes(mVolumeInfos);
+ }
+
+ public void adjustVolumeByOne(int groupId, boolean up) {
+ if (mCarAudioManager == null) {
+ Log.e(TAG, "CarAudioManager is null");
+ return;
+ }
+ int current = mCarAudioManager.getGroupVolume(mZoneId, groupId);
+ int volume = current + (up ? 1 : -1);
+ mCarAudioManager.setGroupVolume(mZoneId, groupId, volume,
+ AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND);
+ if (DEBUG) {
+ Log.d(TAG, "Set group " + groupId + " volume " + volume + " in audio zone "
+ + mZoneId);
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_VOLUME_CHANGED));
+ }
+
+ public void requestFocus(int groupId) {
+ // Automatic volume change only works for primary audio zone.
+ if (mZoneId == CarAudioManager.PRIMARY_AUDIO_ZONE) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_REQUEST_FOCUS, groupId));
+ }
+ }
+
+ private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
+ private final int mGroupId;
+ AudioFocusListener(int groupId) {
+ mGroupId = groupId;
+ }
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_FOCUS_CHANGED, mGroupId, 0));
+ } else {
+ Log.e(TAG, "Audio focus request failed");
+ }
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
index f753efe..5c662d4 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
@@ -18,32 +18,37 @@
import android.car.Car;
import android.car.Car.CarServiceLifecycleListener;
import android.car.media.CarAudioManager;
+import android.car.media.CarAudioManager.CarVolumeCallback;
import android.content.Context;
import android.media.AudioManager;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
import android.util.Log;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ListView;
import android.widget.SeekBar;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.viewpager.widget.ViewPager;
import com.google.android.car.kitchensink.R;
+import com.google.android.material.tabs.TabLayout;
-public class VolumeTestFragment extends Fragment {
+import java.util.List;
+
+import javax.annotation.concurrent.GuardedBy;
+
+public final class VolumeTestFragment extends Fragment {
private static final String TAG = "CarVolumeTest";
- private static final int MSG_VOLUME_CHANGED = 0;
- private static final int MSG_REQUEST_FOCUS = 1;
- private static final int MSG_FOCUS_CHANGED= 2;
+ private static final boolean DEBUG = true;
private AudioManager mAudioManager;
- private VolumeAdapter mAdapter;
+ private AudioZoneVolumeTabAdapter mAudioZoneAdapter;
+ @GuardedBy("mLock")
+ private final SparseArray<CarAudioZoneVolumeFragment> mZoneVolumeFragments =
+ new SparseArray<>();
private CarAudioManager mCarAudioManager;
private Car mCar;
@@ -51,58 +56,10 @@
private SeekBar mFader;
private SeekBar mBalance;
- private final Handler mHandler = new VolumeHandler();
+ private TabLayout mZonesTabLayout;
+ private Object mLock = new Object();
- private class VolumeHandler extends Handler {
- private AudioFocusListener mFocusListener;
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_VOLUME_CHANGED:
- initVolumeInfo();
- break;
- case MSG_REQUEST_FOCUS:
- int groupId = msg.arg1;
- if (mFocusListener != null) {
- mAudioManager.abandonAudioFocus(mFocusListener);
- mVolumeInfos[mGroupIdIndexMap.get(groupId)].mHasFocus = false;
- mAdapter.notifyDataSetChanged();
- }
-
- mFocusListener = new AudioFocusListener(groupId);
- mAudioManager.requestAudioFocus(mFocusListener, groupId,
- AudioManager.AUDIOFOCUS_GAIN);
- break;
- case MSG_FOCUS_CHANGED:
- int focusGroupId = msg.arg1;
- mVolumeInfos[mGroupIdIndexMap.get(focusGroupId)].mHasFocus = true;
- mAdapter.refreshVolumes(mVolumeInfos);
- break;
-
- }
- }
- }
-
- private VolumeInfo[] mVolumeInfos = new VolumeInfo[0];
- private SparseIntArray mGroupIdIndexMap = new SparseIntArray();
-
- private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
- private final int mGroupId;
- public AudioFocusListener(int groupId) {
- mGroupId = groupId;
- }
- @Override
- public void onAudioFocusChange(int focusChange) {
- if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_FOCUS_CHANGED, mGroupId, 0));
- } else {
- Log.e(TAG, "Audio focus request failed");
- }
- }
- }
-
- public static class VolumeInfo {
+ public static class CarAudioZoneVolumeInfo {
public int mGroupId;
public String mId;
public String mMax;
@@ -110,30 +67,63 @@
public boolean mHasFocus;
}
+ private final class CarVolumeChangeListener extends CarVolumeCallback {
+ @Override
+ public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
+ if (DEBUG) {
+ Log.d(TAG, "onGroupVolumeChanged volume changed for zone "
+ + zoneId);
+ }
+ synchronized (mLock) {
+ CarAudioZoneVolumeFragment fragment = mZoneVolumeFragments.get(zoneId);
+ if (fragment != null) {
+ fragment.sendChangeMessage();
+ }
+ }
+ }
+
+ @Override
+ public void onMasterMuteChanged(int zoneId, int flags) {
+ if (DEBUG) {
+ Log.d(TAG, "onMasterMuteChanged master mute "
+ + mAudioManager.isMasterMute());
+ }
+ }
+ }
+
+ private final CarVolumeCallback mCarVolumeCallback = new CarVolumeChangeListener();
+
private CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
if (!ready) {
- Log.d(TAG, "Disconnect from Car Service");
+ if (DEBUG) {
+ Log.d(TAG, "Disconnect from Car Service");
+ }
return;
}
- Log.d(TAG, "Connected to Car Service");
+ if (DEBUG) {
+ Log.d(TAG, "Connected to Car Service");
+ }
mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
initVolumeInfo();
+ mCarAudioManager.registerCarVolumeCallback(mCarVolumeCallback);
};
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.volume_test, container, false);
-
- ListView volumeListView = v.findViewById(R.id.volume_list);
mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
- mAdapter = new VolumeAdapter(getContext(), R.layout.volume_item, mVolumeInfos, this);
- volumeListView.setAdapter(mAdapter);
+ View v = inflater.inflate(R.layout.volume_test, container, false);
- v.findViewById(R.id.refresh).setOnClickListener((view) -> initVolumeInfo());
+ mZonesTabLayout = v.findViewById(R.id.zones_tab);
+ ViewPager viewPager = (ViewPager) v.findViewById(R.id.zone_view_pager);
- final SeekBar.OnSeekBarChangeListener seekListener = new SeekBar.OnSeekBarChangeListener() {
+ mAudioZoneAdapter = new AudioZoneVolumeTabAdapter(getChildFragmentManager());
+ viewPager.setAdapter(mAudioZoneAdapter);
+ mZonesTabLayout.setupWithViewPager(viewPager);
+
+ SeekBar.OnSeekBarChangeListener seekListener =
+ new SeekBar.OnSeekBarChangeListener() {
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
final float percent = (progress - 100) / 100.0f;
if (seekBar.getId() == R.id.fade_bar) {
@@ -159,48 +149,20 @@
return v;
}
- public void adjustVolumeByOne(int groupId, boolean up) {
- if (mCarAudioManager == null) {
- Log.e(TAG, "CarAudioManager is null");
- return;
- }
- int current = mCarAudioManager.getGroupVolume(groupId);
- int volume = current + (up ? 1 : -1);
- mCarAudioManager.setGroupVolume(groupId, volume,
- AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND);
- Log.d(TAG, "Set group " + groupId + " volume " + volume);
- }
-
- public void requestFocus(int groupId) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_REQUEST_FOCUS, groupId));
- }
-
private void initVolumeInfo() {
- int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
- mVolumeInfos = new VolumeInfo[volumeGroupCount + 1];
- mGroupIdIndexMap.clear();
- mVolumeInfos[0] = new VolumeInfo();
- mVolumeInfos[0].mId = "Group id";
- mVolumeInfos[0].mCurrent = "Current";
- mVolumeInfos[0].mMax = "Max";
-
- int i = 1;
- for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
- mVolumeInfos[i] = new VolumeInfo();
- mVolumeInfos[i].mGroupId = groupId;
- mGroupIdIndexMap.put(groupId, i);
- mVolumeInfos[i].mId = String.valueOf(groupId);
-
-
- int current = mCarAudioManager.getGroupVolume(groupId);
- int max = mCarAudioManager.getGroupMaxVolume(groupId);
- mVolumeInfos[i].mCurrent = String.valueOf(current);
- mVolumeInfos[i].mMax = String.valueOf(max);
-
- Log.d(TAG, groupId + " max: " + mVolumeInfos[i].mMax + " current: "
- + mVolumeInfos[i].mCurrent);
- i++;
+ synchronized (mLock) {
+ List<Integer> audioZoneIds = mCarAudioManager.getAudioZoneIds();
+ for (int index = 0; index < audioZoneIds.size(); index++) {
+ int zoneId = audioZoneIds.get(index);
+ CarAudioZoneVolumeFragment fragment =
+ new CarAudioZoneVolumeFragment(zoneId, mCarAudioManager, mAudioManager);
+ mZonesTabLayout.addTab(mZonesTabLayout.newTab().setText("Audio Zone " + zoneId));
+ mAudioZoneAdapter.addFragment(fragment, "Audio Zone " + zoneId);
+ if (DEBUG) {
+ Log.d(TAG, "Adding audio volume for zone " + zoneId);
+ }
+ mZoneVolumeFragments.put(zoneId, fragment);
+ }
}
- mAdapter.refreshVolumes(mVolumeInfos);
}
}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java b/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
index 58b7f71..cc9ecd3 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
@@ -15,6 +15,8 @@
*/
package com.android.car.audio;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -22,15 +24,18 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.app.ActivityManager;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.os.UserHandle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.primitives.Ints;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -40,22 +45,26 @@
import java.util.Map;
@RunWith(AndroidJUnit4.class)
-public class CarVolumeGroupTest {
+public class CarVolumeGroupTest extends AbstractExtendedMockitoTestCase{
private static final int STEP_VALUE = 2;
private static final int MIN_GAIN = 0;
private static final int MAX_GAIN = 5;
private static final int DEFAULT_GAIN = 0;
+ private static final int TEST_USER_10 = 10;
+ private static final int TEST_USER_11 = 11;
private static final String OTHER_ADDRESS = "other_address";
private static final String MEDIA_DEVICE_ADDRESS = "music";
private static final String NAVIGATION_DEVICE_ADDRESS = "navigation";
- @Rule
- public final ExpectedException thrown = ExpectedException.none();
-
private CarAudioDeviceInfo mMediaDevice;
private CarAudioDeviceInfo mNavigationDevice;
+ @Override
+ protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+ session.spyStatic(ActivityManager.class);
+ }
+
@Before
public void setUp() {
mMediaDevice = generateCarAudioDeviceInfo(MEDIA_DEVICE_ADDRESS);
@@ -90,9 +99,10 @@
NAVIGATION_DEVICE_ADDRESS, STEP_VALUE + 1,
MIN_GAIN, MAX_GAIN);
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("Gain controls within one group must have same step value");
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, differentStepValueDevice);
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> carVolumeGroup.bind(CarAudioContext.NAVIGATION, differentStepValueDevice));
+ assertThat(thrown).hasMessageThat()
+ .contains("Gain controls within one group must have same step value");
}
@Test
@@ -153,11 +163,10 @@
carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice);
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage(
- "Context NAVIGATION has already been bound to " + MEDIA_DEVICE_ADDRESS);
-
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice);
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice));
+ assertThat(thrown).hasMessageThat()
+ .contains("Context NAVIGATION has already been bound to " + MEDIA_DEVICE_ADDRESS);
}
@Test
@@ -241,20 +250,18 @@
public void setCurrentGainIndex_checksNewGainIsAboveMin() {
CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("Gain out of range (0:5) -2index -1");
-
- carVolumeGroup.setCurrentGainIndex(-1);
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> carVolumeGroup.setCurrentGainIndex(-1));
+ assertThat(thrown).hasMessageThat().contains("Gain out of range (0:5) -2index -1");
}
@Test
public void setCurrentGainIndex_checksNewGainIsBelowMax() {
CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("Gain out of range (0:5) 6index 3");
-
- carVolumeGroup.setCurrentGainIndex(3);
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> carVolumeGroup.setCurrentGainIndex(3));
+ assertThat(thrown).hasMessageThat().contains("Gain out of range (0:5) 6index 3");
}
@Test
@@ -280,12 +287,12 @@
public void loadVolumesForUser_setsCurrentGainIndexForUser() {
List<Integer> users = new ArrayList<>();
- users.add(10);
- users.add(11);
+ users.add(TEST_USER_10);
+ users.add(TEST_USER_11);
Map<Integer, Integer> storedGainIndex = new HashMap<>();
- storedGainIndex.put(10, 2);
- storedGainIndex.put(11, 0);
+ storedGainIndex.put(TEST_USER_10, 2);
+ storedGainIndex.put(TEST_USER_11, 0);
CarAudioSettings settings =
generateCarAudioSettings(users, 0 , 0, storedGainIndex);
@@ -294,11 +301,11 @@
CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
- carVolumeGroup.loadVolumesForUser(10);
+ carVolumeGroup.loadVolumesForUser(TEST_USER_10);
assertEquals(2, carVolumeGroup.getCurrentGainIndex());
- carVolumeGroup.loadVolumesForUser(11);
+ carVolumeGroup.loadVolumesForUser(TEST_USER_11);
assertEquals(0, carVolumeGroup.getCurrentGainIndex());
}
@@ -306,7 +313,7 @@
@Test
public void loadUserStoredGainIndex_setsCurrentGainIndexToDefault() {
CarAudioSettings settings =
- generateCarAudioSettings(0, 0 , 0, 10);
+ generateCarAudioSettings(TEST_USER_10, 0, 0, 10);
CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
@@ -323,6 +330,50 @@
}
@Test
+ public void setCurrentGainIndex_setsCurrentGainIndexForUser() {
+ List<Integer> users = new ArrayList<>();
+ users.add(TEST_USER_11);
+
+ Map<Integer, Integer> storedGainIndex = new HashMap<>();
+ storedGainIndex.put(TEST_USER_11, 2);
+
+ CarAudioSettings settings =
+ generateCarAudioSettings(users, 0 , 0, storedGainIndex);
+ CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+ CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
+ NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
+ carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
+ carVolumeGroup.loadVolumesForUser(TEST_USER_11);
+
+ carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
+
+ verify(settings).storeVolumeGainIndexForUser(TEST_USER_11, 0, 0, MIN_GAIN);
+ }
+
+ @Test
+ public void setCurrentGainIndex_setsCurrentGainIndexForDefaultUser() {
+ List<Integer> users = new ArrayList<>();
+ users.add(UserHandle.USER_CURRENT);
+
+ Map<Integer, Integer> storedGainIndex = new HashMap<>();
+ storedGainIndex.put(UserHandle.USER_CURRENT, 2);
+
+ CarAudioSettings settings =
+ generateCarAudioSettings(users, 0 , 0, storedGainIndex);
+ CarVolumeGroup carVolumeGroup = new CarVolumeGroup(settings, 0, 0);
+
+ CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
+ NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
+ carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
+
+ carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
+
+ verify(settings)
+ .storeVolumeGainIndexForUser(UserHandle.USER_CURRENT, 0, 0, MIN_GAIN);
+ }
+
+ @Test
public void bind_setsCurrentGainIndexToStoredGainIndex() {
CarAudioSettings settings =
generateCarAudioSettings(0 , 0, 2);
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioSettingsUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioSettingsUnitTest.java
index 89fd6ac..cf1b7fd 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioSettingsUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioSettingsUnitTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import android.car.media.CarAudioManager;
import android.car.settings.CarSettings;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.content.ContentResolver;
@@ -33,6 +34,10 @@
public class CarAudioSettingsUnitTest extends AbstractExtendedMockitoTestCase {
private static final int TEST_USER_ID_1 = 11;
+ private static final int TEST_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE;
+ private static final int TEST_GROUP_ID = 0;
+ private static final int TEST_GAIN_INDEX = 10;
+ private static final String TEST_GAIN_INDEX_KEY = "android.car.VOLUME_GROUP/0";
@Mock
@@ -61,6 +66,25 @@
.isTrue();
}
+ @Test
+ public void getStoredVolumeGainIndexForUser_returnsSavedValue() {
+ setStoredVolumeGainIndexForUser(TEST_GAIN_INDEX);
+
+ assertThat(mCarAudioSettings.getStoredVolumeGainIndexForUser(TEST_USER_ID_1, TEST_ZONE_ID,
+ TEST_GROUP_ID)).isEqualTo(TEST_GAIN_INDEX);
+ }
+
+ @Test
+ public void storedVolumeGainIndexForUser_savesValue() {
+ mCarAudioSettings.storeVolumeGainIndexForUser(TEST_USER_ID_1, TEST_ZONE_ID,
+ TEST_GROUP_ID, TEST_GAIN_INDEX);
+ assertThat(getSettingsInt(TEST_GAIN_INDEX_KEY)).isEqualTo(TEST_GAIN_INDEX);
+ }
+
+ private void setStoredVolumeGainIndexForUser(int gainIndexForUser) {
+ putSettingsInt(TEST_GAIN_INDEX_KEY, gainIndexForUser);
+ }
+
private void setRejectNavigationOnCallSettingsValues(int settingsValue) {
putSettingsInt(CarSettings.Secure.KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL,
settingsValue);