Add wrapper for AudioDeviceVolumeManager in the HDMI framework

Adding a wrapper interface for AudioDeviceVolumeManager allows the class
to be faked. This allows tests that instantiate HdmiControlService to
avoid also instantiating AudioDeviceVolumeManager, which can affect the
state of AudioService on the DUT.

Using an interface instead of inheritance allows constructors to be more
independent, and allows the same class to "wrap" multiple managers in
the future if they need to share state (e.g. AudioManager and
AudioDeviceVolumeManager).

Bug: 217923086
Test: atest com.android.server.hdmi
Change-Id: I673ab6e3a6acbbc6af8998e2f5c048fec59ad47a
This commit is contained in:
Yan Han
2022-03-29 15:25:08 +02:00
parent 30eec56eac
commit 6613fd3fbb
28 changed files with 275 additions and 35 deletions

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.hdmi;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.content.Context;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceVolumeManager;
import android.media.VolumeInfo;
import java.util.concurrent.Executor;
/**
* Wrapper for {@link AudioDeviceVolumeManager}. Creates an instance of the class and directly
* passes method calls to that instance.
*/
public class AudioDeviceVolumeManagerWrapper
implements AudioDeviceVolumeManagerWrapperInterface {
private static final String TAG = "AudioDeviceVolumeManagerWrapper";
private final AudioDeviceVolumeManager mAudioDeviceVolumeManager;
public AudioDeviceVolumeManagerWrapper(Context context) {
mAudioDeviceVolumeManager = new AudioDeviceVolumeManager(context);
}
@Override
public void addOnDeviceVolumeBehaviorChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener)
throws SecurityException {
mAudioDeviceVolumeManager.addOnDeviceVolumeBehaviorChangedListener(executor, listener);
}
@Override
public void removeOnDeviceVolumeBehaviorChangedListener(
@NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener) {
mAudioDeviceVolumeManager.removeOnDeviceVolumeBehaviorChangedListener(listener);
}
@Override
public void setDeviceAbsoluteVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
@NonNull @CallbackExecutor Executor executor,
@NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment) {
mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor,
vclistener, handlesVolumeAdjustment);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.hdmi;
import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceVolumeManager;
import android.media.VolumeInfo;
import java.util.concurrent.Executor;
/**
* Interface with the methods from {@link AudioDeviceVolumeManager} used by the HDMI framework.
* Allows the class to be faked for tests.
*/
public interface AudioDeviceVolumeManagerWrapperInterface {
/**
* Wrapper for {@link AudioDeviceVolumeManager#addOnDeviceVolumeBehaviorChangedListener(
* Executor, OnDeviceVolumeBehaviorChangedListener)}
*/
void addOnDeviceVolumeBehaviorChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
/**
* Wrapper for {@link AudioDeviceVolumeManager#removeOnDeviceVolumeBehaviorChangedListener(
* OnDeviceVolumeBehaviorChangedListener)}
*/
void removeOnDeviceVolumeBehaviorChangedListener(
@NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
/**
* Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior(
* AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
*/
void setDeviceAbsoluteVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
@NonNull @CallbackExecutor Executor executor,
@NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment);
}

View File

@@ -410,6 +410,9 @@ public class HdmiControlService extends SystemService {
@Nullable
private PowerManagerInternalWrapper mPowerManagerInternal;
@Nullable
private AudioDeviceVolumeManagerWrapperInterface mAudioDeviceVolumeManager;
@Nullable
private Looper mIoLooper;
@@ -439,11 +442,21 @@ public class HdmiControlService extends SystemService {
private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
@VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes) {
/**
* Constructor for testing.
*
* It's critical to use a fake AudioDeviceVolumeManager because a normally instantiated
* AudioDeviceVolumeManager can access the "real" AudioService on the DUT.
*
* @see FakeAudioDeviceVolumeManagerWrapper
*/
@VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes,
AudioDeviceVolumeManagerWrapperInterface audioDeviceVolumeManager) {
super(context);
mLocalDevices = deviceTypes;
mSettingsObserver = new SettingsObserver(mHandler);
mHdmiCecConfig = new HdmiCecConfig(context);
mAudioDeviceVolumeManager = audioDeviceVolumeManager;
}
public HdmiControlService(Context context) {
@@ -744,6 +757,8 @@ public class HdmiControlService extends SystemService {
Context.TV_INPUT_SERVICE);
mPowerManager = new PowerManagerWrapper(getContext());
mPowerManagerInternal = new PowerManagerInternalWrapper();
mAudioDeviceVolumeManager =
new AudioDeviceVolumeManagerWrapper(getContext());
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
runOnServiceThread(this::bootCompleted);
}
@@ -3086,15 +3101,17 @@ public class HdmiControlService extends SystemService {
private void announceHdmiCecVolumeControlFeatureChange(
@HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
assertRunOnServiceThread();
mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
try {
listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
} catch (RemoteException e) {
Slog.e(TAG,
"Failed to report HdmiControlVolumeControlStatusChange: "
+ hdmiCecVolumeControl);
}
});
synchronized (mLock) {
mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
try {
listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
} catch (RemoteException e) {
Slog.e(TAG,
"Failed to report HdmiControlVolumeControlStatusChange: "
+ hdmiCecVolumeControl);
}
});
}
}
public HdmiCecLocalDeviceTv tv() {
@@ -3135,6 +3152,14 @@ public class HdmiControlService extends SystemService {
return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
}
/**
* Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
*/
@Nullable
private AudioDeviceVolumeManagerWrapperInterface getAudioDeviceVolumeManager() {
return mAudioDeviceVolumeManager;
}
boolean isControlEnabled() {
synchronized (mLock) {
return mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;

View File

@@ -61,7 +61,8 @@ public class ActiveSourceActionTest {
public void setUp() throws Exception {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {

View File

@@ -68,7 +68,8 @@ public class ArcInitiationActionFromAvrTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
HdmiControlService hdmiControlService =
new HdmiControlService(mContextSpy, Collections.emptyList()) {
new HdmiControlService(mContextSpy, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isPowerStandby() {
return false;

View File

@@ -68,7 +68,8 @@ public class ArcTerminationActionFromAvrTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
HdmiControlService hdmiControlService =
new HdmiControlService(mContextSpy, Collections.emptyList()) {
new HdmiControlService(mContextSpy, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return mAudioManager;

View File

@@ -56,7 +56,7 @@ public class DetectTvSystemAudioModeSupportActionTest {
mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234);
HdmiControlService hdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.emptyList()) {
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void sendCecCommand(

View File

@@ -78,7 +78,8 @@ public class DevicePowerStatusActionTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {

View File

@@ -100,7 +100,7 @@ public class DeviceSelectActionFromPlaybackTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.emptyList()) {
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;

View File

@@ -109,7 +109,7 @@ public class DeviceSelectActionFromTvTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.emptyList()) {
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.hdmi;
import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceVolumeManager;
import android.media.AudioManager;
import android.media.VolumeInfo;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;
/**
* Wrapper for {@link AudioDeviceVolumeManager} that stubs its methods. Useful for testing.
*/
public class FakeAudioDeviceVolumeManagerWrapper implements
AudioDeviceVolumeManagerWrapperInterface {
private final Set<OnDeviceVolumeBehaviorChangedListener> mVolumeBehaviorListeners;
public FakeAudioDeviceVolumeManagerWrapper() {
mVolumeBehaviorListeners = new HashSet<>();
}
@Override
public void addOnDeviceVolumeBehaviorChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnDeviceVolumeBehaviorChangedListener listener)
throws SecurityException {
mVolumeBehaviorListeners.add(listener);
}
@Override
public void removeOnDeviceVolumeBehaviorChangedListener(
@NonNull OnDeviceVolumeBehaviorChangedListener listener) {
mVolumeBehaviorListeners.remove(listener);
}
@Override
public void setDeviceAbsoluteVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment) {
// Notify all volume behavior listeners that the device adopted absolute volume behavior
for (OnDeviceVolumeBehaviorChangedListener listener : mVolumeBehaviorListeners) {
listener.onDeviceVolumeBehaviorChanged(device,
AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
}
}
}

View File

@@ -89,7 +89,8 @@ public class HdmiCecAtomLoggingTest {
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();

View File

@@ -101,7 +101,8 @@ public class HdmiCecControllerTest {
mMyLooper = mTestLooper.getLooper();
mHdmiControlServiceSpy = spy(new HdmiControlService(
InstrumentationRegistry.getTargetContext(), Collections.emptyList()));
InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()));
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper();
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper();
doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();

View File

@@ -88,7 +88,7 @@ public class HdmiCecLocalDeviceAudioSystemTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.emptyList()) {
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {

View File

@@ -92,7 +92,7 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.emptyList()) {
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void wakeUp() {
mWokenUp = true;

View File

@@ -137,7 +137,8 @@ public class HdmiCecLocalDeviceTest {
Context context = InstrumentationRegistry.getTargetContext();
mHdmiControlService =
new HdmiControlService(context, Collections.emptyList()) {
new HdmiControlService(context, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return isControlEnabled;

View File

@@ -125,7 +125,7 @@ public class HdmiCecLocalDeviceTvTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.emptyList()) {
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void wakeUp() {
mWokenUp = true;

View File

@@ -51,7 +51,8 @@ public class HdmiCecMessageValidatorTest {
@Before
public void setUp() throws Exception {
HdmiControlService mHdmiControlService = new HdmiControlService(
InstrumentationRegistry.getTargetContext(), Collections.emptyList());
InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper());
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
}

View File

@@ -67,7 +67,8 @@ public class HdmiCecNetworkTest {
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList()) {
mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
mDeviceEventListenerStatuses.add(status);

View File

@@ -65,7 +65,8 @@ public class HdmiCecPowerStatusControllerTest {
Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
Looper myLooper = mTestLooper.getLooper();
mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList()) {
mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;

View File

@@ -91,7 +91,8 @@ public class HdmiControlServiceTest {
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());

View File

@@ -87,7 +87,8 @@ public class OneTouchPlayActionTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {

View File

@@ -68,7 +68,8 @@ public class PowerStatusMonitorActionTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mHdmiControlService = new HdmiControlService(mContextSpy,
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV)) {
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {

View File

@@ -96,8 +96,8 @@ public class RequestSadActionTest {
mMyLooper = mTestLooper.getLooper();
mHdmiControlService =
new HdmiControlService(context,
Collections.emptyList()) {
new HdmiControlService(context, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;

View File

@@ -149,7 +149,7 @@ public class RoutingControlActionTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.emptyList()) {
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;

View File

@@ -81,7 +81,8 @@ public class SetAudioVolumeLevelDiscoveryActionTest {
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());

View File

@@ -69,7 +69,8 @@ public class SystemAudioAutoInitiationActionTest {
Looper myLooper = mTestLooper.getLooper();
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {

View File

@@ -69,7 +69,7 @@ public class SystemAudioInitiationActionFromAvrTest {
Context context = InstrumentationRegistry.getTargetContext();
HdmiControlService hdmiControlService = new HdmiControlService(context,
Collections.emptyList()) {
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void sendCecCommand(
HdmiCecMessage command, @Nullable SendMessageCallback callback) {