Merge changes from topics "presubmit-am-7ed5c317d7b347f1a59eb332f20e8522", "presubmit-am-86ce18b053454a4db98a349f8b39985e", "presubmit-am-94d3142a4e1c418fadfd6eca6208920a", "presubmit-am-a466ed1382304d549cb550a5cae804e6" into tm-dev am: a62f32758c
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17140464 Change-Id: I0171243b276c6bab410b190bde052a7e011f675d Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to query and track the audio status of the System Audio device when enabling or using
|
||||||
|
* Absolute Volume Control. Must be removed when AVC is disabled. Performs two main functions:
|
||||||
|
* 1. When enabling AVC: queries the starting audio status of the System Audio device and
|
||||||
|
* enables the feature upon receiving a response.
|
||||||
|
* 2. While AVC is enabled: monitors <Report Audio Status> messages from the System Audio device and
|
||||||
|
* notifies AudioService if the audio status changes.
|
||||||
|
*/
|
||||||
|
final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction {
|
||||||
|
private static final String TAG = "AbsoluteVolumeAudioStatusAction";
|
||||||
|
|
||||||
|
private int mInitialAudioStatusRetriesLeft = 2;
|
||||||
|
|
||||||
|
private static final int STATE_WAIT_FOR_INITIAL_AUDIO_STATUS = 1;
|
||||||
|
private static final int STATE_MONITOR_AUDIO_STATUS = 2;
|
||||||
|
|
||||||
|
private final int mTargetAddress;
|
||||||
|
|
||||||
|
private AudioStatus mLastAudioStatus;
|
||||||
|
|
||||||
|
AbsoluteVolumeAudioStatusAction(HdmiCecLocalDevice source, int targetAddress) {
|
||||||
|
super(source);
|
||||||
|
mTargetAddress = targetAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean start() {
|
||||||
|
mState = STATE_WAIT_FOR_INITIAL_AUDIO_STATUS;
|
||||||
|
sendGiveAudioStatus();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateVolume(int volumeIndex) {
|
||||||
|
mLastAudioStatus = new AudioStatus(volumeIndex, mLastAudioStatus.getMute());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendGiveAudioStatus() {
|
||||||
|
addTimer(mState, HdmiConfig.TIMEOUT_MS);
|
||||||
|
sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mTargetAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean processCommand(HdmiCecMessage cmd) {
|
||||||
|
switch (cmd.getOpcode()) {
|
||||||
|
case Constants.MESSAGE_REPORT_AUDIO_STATUS:
|
||||||
|
return handleReportAudioStatus(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
|
||||||
|
if (mTargetAddress != cmd.getSource() || cmd.getParams().length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean mute = HdmiUtils.isAudioStatusMute(cmd);
|
||||||
|
int volume = HdmiUtils.getAudioStatusVolume(cmd);
|
||||||
|
AudioStatus audioStatus = new AudioStatus(volume, mute);
|
||||||
|
if (mState == STATE_WAIT_FOR_INITIAL_AUDIO_STATUS) {
|
||||||
|
localDevice().getService().enableAbsoluteVolumeControl(audioStatus);
|
||||||
|
mState = STATE_MONITOR_AUDIO_STATUS;
|
||||||
|
} else if (mState == STATE_MONITOR_AUDIO_STATUS) {
|
||||||
|
if (audioStatus.getVolume() != mLastAudioStatus.getVolume()) {
|
||||||
|
localDevice().getService().notifyAvcVolumeChange(audioStatus.getVolume());
|
||||||
|
}
|
||||||
|
if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
|
||||||
|
localDevice().getService().notifyAvcMuteChange(audioStatus.getMute());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mLastAudioStatus = audioStatus;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void handleTimerEvent(int state) {
|
||||||
|
if (mState != state) {
|
||||||
|
return;
|
||||||
|
} else if (mInitialAudioStatusRetriesLeft > 0) {
|
||||||
|
mInitialAudioStatusRetriesLeft--;
|
||||||
|
sendGiveAudioStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
67
services/core/java/com/android/server/hdmi/AudioStatus.java
Normal file
67
services/core/java/com/android/server/hdmi/AudioStatus.java
Normal 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.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable representation of the information in the [Audio Status] operand:
|
||||||
|
* volume status (0 <= N <= 100) and mute status (muted or unmuted).
|
||||||
|
*/
|
||||||
|
public class AudioStatus {
|
||||||
|
public static final int MAX_VOLUME = 100;
|
||||||
|
public static final int MIN_VOLUME = 0;
|
||||||
|
|
||||||
|
int mVolume;
|
||||||
|
boolean mMute;
|
||||||
|
|
||||||
|
public AudioStatus(int volume, boolean mute) {
|
||||||
|
mVolume = volume;
|
||||||
|
mMute = mute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVolume() {
|
||||||
|
return mVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getMute() {
|
||||||
|
return mMute;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (!(obj instanceof AudioStatus)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioStatus other = (AudioStatus) obj;
|
||||||
|
return mVolume == other.mVolume
|
||||||
|
&& mMute == other.mMute;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(mVolume, mMute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AudioStatus mVolume:" + mVolume + " mMute:" + mMute;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,6 +118,7 @@ final class Constants {
|
|||||||
MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
|
MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
|
||||||
MESSAGE_GIVE_AUDIO_STATUS,
|
MESSAGE_GIVE_AUDIO_STATUS,
|
||||||
MESSAGE_SET_SYSTEM_AUDIO_MODE,
|
MESSAGE_SET_SYSTEM_AUDIO_MODE,
|
||||||
|
MESSAGE_SET_AUDIO_VOLUME_LEVEL,
|
||||||
MESSAGE_REPORT_AUDIO_STATUS,
|
MESSAGE_REPORT_AUDIO_STATUS,
|
||||||
MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS,
|
MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS,
|
||||||
MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
|
MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
|
||||||
@@ -197,9 +198,9 @@ final class Constants {
|
|||||||
static final int MESSAGE_SYSTEM_AUDIO_MODE_REQUEST = 0x70;
|
static final int MESSAGE_SYSTEM_AUDIO_MODE_REQUEST = 0x70;
|
||||||
static final int MESSAGE_GIVE_AUDIO_STATUS = 0x71;
|
static final int MESSAGE_GIVE_AUDIO_STATUS = 0x71;
|
||||||
static final int MESSAGE_SET_SYSTEM_AUDIO_MODE = 0x72;
|
static final int MESSAGE_SET_SYSTEM_AUDIO_MODE = 0x72;
|
||||||
|
static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
|
||||||
static final int MESSAGE_REPORT_AUDIO_STATUS = 0x7A;
|
static final int MESSAGE_REPORT_AUDIO_STATUS = 0x7A;
|
||||||
static final int MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D;
|
static final int MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D;
|
||||||
static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
|
|
||||||
static final int MESSAGE_SYSTEM_AUDIO_MODE_STATUS = 0x7E;
|
static final int MESSAGE_SYSTEM_AUDIO_MODE_STATUS = 0x7E;
|
||||||
static final int MESSAGE_ROUTING_CHANGE = 0x80;
|
static final int MESSAGE_ROUTING_CHANGE = 0x80;
|
||||||
static final int MESSAGE_ROUTING_INFORMATION = 0x81;
|
static final int MESSAGE_ROUTING_INFORMATION = 0x81;
|
||||||
|
|||||||
@@ -303,6 +303,13 @@ abstract class HdmiCecLocalDevice {
|
|||||||
if (dispatchMessageToAction(message)) {
|
if (dispatchMessageToAction(message)) {
|
||||||
return Constants.HANDLED;
|
return Constants.HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a message type has its own class, all valid messages of that type
|
||||||
|
// will be represented by an instance of that class.
|
||||||
|
if (message instanceof SetAudioVolumeLevelMessage) {
|
||||||
|
return handleSetAudioVolumeLevel((SetAudioVolumeLevelMessage) message);
|
||||||
|
}
|
||||||
|
|
||||||
switch (message.getOpcode()) {
|
switch (message.getOpcode()) {
|
||||||
case Constants.MESSAGE_ACTIVE_SOURCE:
|
case Constants.MESSAGE_ACTIVE_SOURCE:
|
||||||
return handleActiveSource(message);
|
return handleActiveSource(message);
|
||||||
@@ -637,6 +644,11 @@ abstract class HdmiCecLocalDevice {
|
|||||||
return Constants.NOT_HANDLED;
|
return Constants.NOT_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Constants.HandleMessageResult
|
||||||
|
protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
|
||||||
|
return Constants.NOT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
@Constants.RcProfile
|
@Constants.RcProfile
|
||||||
protected abstract int getRcProfile();
|
protected abstract int getRcProfile();
|
||||||
|
|
||||||
@@ -1002,6 +1014,57 @@ abstract class HdmiCecLocalDevice {
|
|||||||
action.start();
|
action.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addAvcAudioStatusAction(int targetAddress) {
|
||||||
|
if (!hasAction(AbsoluteVolumeAudioStatusAction.class)) {
|
||||||
|
addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeAvcAudioStatusAction() {
|
||||||
|
removeAction(AbsoluteVolumeAudioStatusAction.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateAvcVolume(int volumeIndex) {
|
||||||
|
for (AbsoluteVolumeAudioStatusAction action :
|
||||||
|
getActions(AbsoluteVolumeAudioStatusAction.class)) {
|
||||||
|
action.updateVolume(volumeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things
|
||||||
|
* in parallel: send <Give Features> (to get <Report Features> in response),
|
||||||
|
* and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response).
|
||||||
|
*/
|
||||||
|
@ServiceThreadOnly
|
||||||
|
void queryAvcSupport(int targetAddress) {
|
||||||
|
assertRunOnServiceThread();
|
||||||
|
|
||||||
|
// Send <Give Features> if using CEC 2.0 or above.
|
||||||
|
if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures(
|
||||||
|
getDeviceInfo().getLogicalAddress(), targetAddress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target
|
||||||
|
// device, start one.
|
||||||
|
List<SetAudioVolumeLevelDiscoveryAction> savlDiscoveryActions =
|
||||||
|
getActions(SetAudioVolumeLevelDiscoveryAction.class);
|
||||||
|
if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) {
|
||||||
|
addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress,
|
||||||
|
new IHdmiControlCallback.Stub() {
|
||||||
|
@Override
|
||||||
|
public void onComplete(int result) {
|
||||||
|
if (result == HdmiControlManager.RESULT_SUCCESS) {
|
||||||
|
getService().checkAndUpdateAbsoluteVolumeControlState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ServiceThreadOnly
|
@ServiceThreadOnly
|
||||||
void startQueuedActions() {
|
void startQueuedActions() {
|
||||||
assertRunOnServiceThread();
|
assertRunOnServiceThread();
|
||||||
@@ -1205,6 +1268,9 @@ abstract class HdmiCecLocalDevice {
|
|||||||
*/
|
*/
|
||||||
protected void disableDevice(
|
protected void disableDevice(
|
||||||
boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
|
boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
|
||||||
|
removeAction(AbsoluteVolumeAudioStatusAction.class);
|
||||||
|
removeAction(SetAudioVolumeLevelDiscoveryAction.class);
|
||||||
|
|
||||||
mPendingActionClearedCallback =
|
mPendingActionClearedCallback =
|
||||||
new PendingActionClearedCallback() {
|
new PendingActionClearedCallback() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
|
|||||||
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
|
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
|
||||||
removeAction(OneTouchPlayAction.class);
|
removeAction(OneTouchPlayAction.class);
|
||||||
removeAction(DevicePowerStatusAction.class);
|
removeAction(DevicePowerStatusAction.class);
|
||||||
|
removeAction(AbsoluteVolumeAudioStatusAction.class);
|
||||||
|
|
||||||
super.disableDevice(initiatedByCec, callback);
|
super.disableDevice(initiatedByCec, callback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1166,6 +1166,19 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
return Constants.HANDLED;
|
return Constants.HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Constants.HandleMessageResult
|
||||||
|
protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
|
||||||
|
// <Set Audio Volume Level> should only be sent to the System Audio device, so we don't
|
||||||
|
// handle it when System Audio Mode is enabled.
|
||||||
|
if (mService.isSystemAudioActivated()) {
|
||||||
|
return Constants.ABORT_NOT_IN_CORRECT_MODE;
|
||||||
|
} else {
|
||||||
|
mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0);
|
||||||
|
return Constants.HANDLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void announceOneTouchRecordResult(int recorderAddress, int result) {
|
void announceOneTouchRecordResult(int recorderAddress, int result) {
|
||||||
mService.invokeOneTouchRecordResult(recorderAddress, result);
|
mService.invokeOneTouchRecordResult(recorderAddress, result);
|
||||||
}
|
}
|
||||||
@@ -1202,6 +1215,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
|
return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the audio output device used for System Audio Mode.
|
||||||
|
*/
|
||||||
|
AudioDeviceAttributes getSystemAudioOutputDevice() {
|
||||||
|
return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ServiceThreadOnly
|
@ServiceThreadOnly
|
||||||
void handleRemoveActiveRoutingPath(int path) {
|
void handleRemoveActiveRoutingPath(int path) {
|
||||||
@@ -1296,6 +1316,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
removeAction(OneTouchRecordAction.class);
|
removeAction(OneTouchRecordAction.class);
|
||||||
removeAction(TimerRecordingAction.class);
|
removeAction(TimerRecordingAction.class);
|
||||||
removeAction(NewDeviceAction.class);
|
removeAction(NewDeviceAction.class);
|
||||||
|
removeAction(AbsoluteVolumeAudioStatusAction.class);
|
||||||
|
|
||||||
disableSystemAudioIfExist();
|
disableSystemAudioIfExist();
|
||||||
disableArcIfExist();
|
disableArcIfExist();
|
||||||
@@ -1318,7 +1339,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
removeAction(SystemAudioActionFromAvr.class);
|
removeAction(SystemAudioActionFromAvr.class);
|
||||||
removeAction(SystemAudioActionFromTv.class);
|
removeAction(SystemAudioActionFromTv.class);
|
||||||
removeAction(SystemAudioAutoInitiationAction.class);
|
removeAction(SystemAudioAutoInitiationAction.class);
|
||||||
removeAction(SystemAudioStatusAction.class);
|
|
||||||
removeAction(VolumeControlAction.class);
|
removeAction(VolumeControlAction.class);
|
||||||
|
|
||||||
if (!mService.isControlEnabled()) {
|
if (!mService.isControlEnabled()) {
|
||||||
@@ -1589,6 +1609,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
|
return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
|
||||||
.setRecordTvScreenSupport(FEATURE_SUPPORTED)
|
.setRecordTvScreenSupport(FEATURE_SUPPORTED)
|
||||||
.setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
|
.setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
|
||||||
|
.setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -262,6 +262,8 @@ public class HdmiCecMessage {
|
|||||||
return "Give Audio Status";
|
return "Give Audio Status";
|
||||||
case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
|
case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
|
||||||
return "Set System Audio Mode";
|
return "Set System Audio Mode";
|
||||||
|
case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL:
|
||||||
|
return "Set Audio Volume Level";
|
||||||
case Constants.MESSAGE_REPORT_AUDIO_STATUS:
|
case Constants.MESSAGE_REPORT_AUDIO_STATUS:
|
||||||
return "Report Audio Status";
|
return "Report Audio Status";
|
||||||
case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS:
|
case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS:
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ public class HdmiCecNetwork {
|
|||||||
// The addition of a local device should not notify listeners
|
// The addition of a local device should not notify listeners
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
|
||||||
if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
|
if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
|
||||||
// Don't notify listeners of devices that haven't reported their physical address yet
|
// Don't notify listeners of devices that haven't reported their physical address yet
|
||||||
return;
|
return;
|
||||||
@@ -383,7 +384,7 @@ public class HdmiCecNetwork {
|
|||||||
final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) {
|
final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) {
|
||||||
assertRunOnServiceThread();
|
assertRunOnServiceThread();
|
||||||
HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
|
HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
|
||||||
|
mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
|
||||||
localDevice.mCecMessageCache.flushMessagesFrom(address);
|
localDevice.mCecMessageCache.flushMessagesFrom(address);
|
||||||
if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
|
if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
|
||||||
// Don't notify listeners of devices that haven't reported their physical address yet
|
// Don't notify listeners of devices that haven't reported their physical address yet
|
||||||
@@ -586,6 +587,8 @@ public class HdmiCecNetwork {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
updateCecDevice(newDeviceInfo);
|
updateCecDevice(newDeviceInfo);
|
||||||
|
|
||||||
|
mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ServiceThreadOnly
|
@ServiceThreadOnly
|
||||||
@@ -617,6 +620,8 @@ public class HdmiCecNetwork {
|
|||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
updateCecDevice(newDeviceInfo);
|
updateCecDevice(newDeviceInfo);
|
||||||
|
|
||||||
|
mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
|
|||||||
import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
|
import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
import android.annotation.IntDef;
|
||||||
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
@@ -38,6 +39,7 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
||||||
|
import android.hardware.hdmi.DeviceFeatures;
|
||||||
import android.hardware.hdmi.HdmiControlManager;
|
import android.hardware.hdmi.HdmiControlManager;
|
||||||
import android.hardware.hdmi.HdmiDeviceInfo;
|
import android.hardware.hdmi.HdmiDeviceInfo;
|
||||||
import android.hardware.hdmi.HdmiHotplugEvent;
|
import android.hardware.hdmi.HdmiHotplugEvent;
|
||||||
@@ -56,7 +58,12 @@ import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
|
|||||||
import android.hardware.hdmi.IHdmiVendorCommandListener;
|
import android.hardware.hdmi.IHdmiVendorCommandListener;
|
||||||
import android.hardware.tv.cec.V1_0.OptionKey;
|
import android.hardware.tv.cec.V1_0.OptionKey;
|
||||||
import android.hardware.tv.cec.V1_0.SendMessageResult;
|
import android.hardware.tv.cec.V1_0.SendMessageResult;
|
||||||
|
import android.media.AudioAttributes;
|
||||||
|
import android.media.AudioDeviceAttributes;
|
||||||
|
import android.media.AudioDeviceInfo;
|
||||||
|
import android.media.AudioDeviceVolumeManager;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
|
import android.media.VolumeInfo;
|
||||||
import android.media.session.MediaController;
|
import android.media.session.MediaController;
|
||||||
import android.media.session.MediaSessionManager;
|
import android.media.session.MediaSessionManager;
|
||||||
import android.media.tv.TvInputManager;
|
import android.media.tv.TvInputManager;
|
||||||
@@ -83,6 +90,7 @@ import android.util.ArrayMap;
|
|||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
@@ -101,6 +109,7 @@ import java.io.PrintWriter;
|
|||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -202,6 +211,27 @@ public class HdmiControlService extends SystemService {
|
|||||||
public @interface WakeReason {
|
public @interface WakeReason {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes(
|
||||||
|
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, "");
|
||||||
|
@VisibleForTesting
|
||||||
|
static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_ARC =
|
||||||
|
new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
|
||||||
|
AudioDeviceInfo.TYPE_HDMI_ARC, "");
|
||||||
|
static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_EARC =
|
||||||
|
new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
|
||||||
|
AudioDeviceInfo.TYPE_HDMI_EARC, "");
|
||||||
|
|
||||||
|
// Audio output devices used for Absolute Volume Control
|
||||||
|
private static final List<AudioDeviceAttributes> AVC_AUDIO_OUTPUT_DEVICES =
|
||||||
|
Collections.unmodifiableList(Arrays.asList(AUDIO_OUTPUT_DEVICE_HDMI,
|
||||||
|
AUDIO_OUTPUT_DEVICE_HDMI_ARC, AUDIO_OUTPUT_DEVICE_HDMI_EARC));
|
||||||
|
|
||||||
|
// AudioAttributes for STREAM_MUSIC
|
||||||
|
@VisibleForTesting
|
||||||
|
static final AudioAttributes STREAM_MUSIC_ATTRIBUTES =
|
||||||
|
new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
|
||||||
|
|
||||||
private final Executor mServiceThreadExecutor = new Executor() {
|
private final Executor mServiceThreadExecutor = new Executor() {
|
||||||
@Override
|
@Override
|
||||||
public void execute(Runnable r) {
|
public void execute(Runnable r) {
|
||||||
@@ -209,6 +239,10 @@ public class HdmiControlService extends SystemService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Executor getServiceThreadExecutor() {
|
||||||
|
return mServiceThreadExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
// Logical address of the active source.
|
// Logical address of the active source.
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
protected final ActiveSource mActiveSource = new ActiveSource();
|
protected final ActiveSource mActiveSource = new ActiveSource();
|
||||||
@@ -222,6 +256,13 @@ public class HdmiControlService extends SystemService {
|
|||||||
@HdmiControlManager.VolumeControl
|
@HdmiControlManager.VolumeControl
|
||||||
private int mHdmiCecVolumeControl;
|
private int mHdmiCecVolumeControl;
|
||||||
|
|
||||||
|
// Caches the volume behaviors of all audio output devices in AVC_AUDIO_OUTPUT_DEVICES.
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private Map<AudioDeviceAttributes, Integer> mAudioDeviceVolumeBehaviors = new HashMap<>();
|
||||||
|
|
||||||
|
// Maximum volume of AudioManager.STREAM_MUSIC. Set upon gaining access to system services.
|
||||||
|
private int mStreamMusicMaxVolume;
|
||||||
|
|
||||||
// Make sure HdmiCecConfig is instantiated and the XMLs are read.
|
// Make sure HdmiCecConfig is instantiated and the XMLs are read.
|
||||||
private HdmiCecConfig mHdmiCecConfig;
|
private HdmiCecConfig mHdmiCecConfig;
|
||||||
|
|
||||||
@@ -410,6 +451,12 @@ public class HdmiControlService extends SystemService {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private PowerManagerInternalWrapper mPowerManagerInternal;
|
private PowerManagerInternalWrapper mPowerManagerInternal;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private AudioManager mAudioManager;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private AudioDeviceVolumeManagerWrapperInterface mAudioDeviceVolumeManager;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Looper mIoLooper;
|
private Looper mIoLooper;
|
||||||
|
|
||||||
@@ -439,11 +486,21 @@ public class HdmiControlService extends SystemService {
|
|||||||
|
|
||||||
private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
|
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);
|
super(context);
|
||||||
mLocalDevices = deviceTypes;
|
mLocalDevices = deviceTypes;
|
||||||
mSettingsObserver = new SettingsObserver(mHandler);
|
mSettingsObserver = new SettingsObserver(mHandler);
|
||||||
mHdmiCecConfig = new HdmiCecConfig(context);
|
mHdmiCecConfig = new HdmiCecConfig(context);
|
||||||
|
mAudioDeviceVolumeManager = audioDeviceVolumeManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HdmiControlService(Context context) {
|
public HdmiControlService(Context context) {
|
||||||
@@ -744,6 +801,14 @@ public class HdmiControlService extends SystemService {
|
|||||||
Context.TV_INPUT_SERVICE);
|
Context.TV_INPUT_SERVICE);
|
||||||
mPowerManager = new PowerManagerWrapper(getContext());
|
mPowerManager = new PowerManagerWrapper(getContext());
|
||||||
mPowerManagerInternal = new PowerManagerInternalWrapper();
|
mPowerManagerInternal = new PowerManagerInternalWrapper();
|
||||||
|
mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
mStreamMusicMaxVolume = getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
|
||||||
|
if (mAudioDeviceVolumeManager == null) {
|
||||||
|
mAudioDeviceVolumeManager =
|
||||||
|
new AudioDeviceVolumeManagerWrapper(getContext());
|
||||||
|
}
|
||||||
|
getAudioDeviceVolumeManager().addOnDeviceVolumeBehaviorChangedListener(
|
||||||
|
mServiceThreadExecutor, this::onDeviceVolumeBehaviorChanged);
|
||||||
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
|
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
|
||||||
runOnServiceThread(this::bootCompleted);
|
runOnServiceThread(this::bootCompleted);
|
||||||
}
|
}
|
||||||
@@ -2419,6 +2484,7 @@ public class HdmiControlService extends SystemService {
|
|||||||
pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
|
pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
|
||||||
pw.println("mIsCecAvailable: " + mIsCecAvailable);
|
pw.println("mIsCecAvailable: " + mIsCecAvailable);
|
||||||
pw.println("mCecVersion: " + mCecVersion);
|
pw.println("mCecVersion: " + mCecVersion);
|
||||||
|
pw.println("mIsAbsoluteVolumeControlEnabled: " + isAbsoluteVolumeControlEnabled());
|
||||||
|
|
||||||
// System settings
|
// System settings
|
||||||
pw.println("System_settings:");
|
pw.println("System_settings:");
|
||||||
@@ -2578,6 +2644,7 @@ public class HdmiControlService extends SystemService {
|
|||||||
@HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
|
@HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
|
||||||
mHdmiCecVolumeControl = hdmiCecVolumeControl;
|
mHdmiCecVolumeControl = hdmiCecVolumeControl;
|
||||||
announceHdmiCecVolumeControlFeatureChange(hdmiCecVolumeControl);
|
announceHdmiCecVolumeControlFeatureChange(hdmiCecVolumeControl);
|
||||||
|
runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the source address to send out commands to devices connected to the current device
|
// Get the source address to send out commands to devices connected to the current device
|
||||||
@@ -3086,15 +3153,17 @@ public class HdmiControlService extends SystemService {
|
|||||||
private void announceHdmiCecVolumeControlFeatureChange(
|
private void announceHdmiCecVolumeControlFeatureChange(
|
||||||
@HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
|
@HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
|
||||||
assertRunOnServiceThread();
|
assertRunOnServiceThread();
|
||||||
mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
|
synchronized (mLock) {
|
||||||
try {
|
mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
|
||||||
listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
|
try {
|
||||||
} catch (RemoteException e) {
|
listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
|
||||||
Slog.e(TAG,
|
} catch (RemoteException e) {
|
||||||
"Failed to report HdmiControlVolumeControlStatusChange: "
|
Slog.e(TAG,
|
||||||
+ hdmiCecVolumeControl);
|
"Failed to report HdmiControlVolumeControlStatusChange: "
|
||||||
}
|
+ hdmiCecVolumeControl);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HdmiCecLocalDeviceTv tv() {
|
public HdmiCecLocalDeviceTv tv() {
|
||||||
@@ -3131,8 +3200,20 @@ public class HdmiControlService extends SystemService {
|
|||||||
HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
|
HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
AudioManager getAudioManager() {
|
AudioManager getAudioManager() {
|
||||||
return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
|
return mAudioManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private AudioDeviceVolumeManagerWrapperInterface getAudioDeviceVolumeManager() {
|
||||||
|
return mAudioDeviceVolumeManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isControlEnabled() {
|
boolean isControlEnabled() {
|
||||||
@@ -3486,6 +3567,7 @@ public class HdmiControlService extends SystemService {
|
|||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
mSystemAudioActivated = on;
|
mSystemAudioActivated = on;
|
||||||
}
|
}
|
||||||
|
runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ServiceThreadOnly
|
@ServiceThreadOnly
|
||||||
@@ -3599,6 +3681,8 @@ public class HdmiControlService extends SystemService {
|
|||||||
device.addActiveSourceHistoryItem(new ActiveSource(logicalAddress, physicalAddress),
|
device.addActiveSourceHistoryItem(new ActiveSource(logicalAddress, physicalAddress),
|
||||||
deviceIsActiveSource, caller);
|
deviceIsActiveSource, caller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method should only be called when the device can be the active source
|
// This method should only be called when the device can be the active source
|
||||||
@@ -3791,4 +3875,332 @@ public class HdmiControlService extends SystemService {
|
|||||||
Slog.e(TAG, "Failed to report setting change", e);
|
Slog.e(TAG, "Failed to report setting change", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for changes to the volume behavior of an audio output device. Caches the
|
||||||
|
* volume behavior of devices used for Absolute Volume Control.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
@ServiceThreadOnly
|
||||||
|
void onDeviceVolumeBehaviorChanged(AudioDeviceAttributes device, int volumeBehavior) {
|
||||||
|
assertRunOnServiceThread();
|
||||||
|
if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
mAudioDeviceVolumeBehaviors.put(device, volumeBehavior);
|
||||||
|
}
|
||||||
|
checkAndUpdateAbsoluteVolumeControlState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for {@link AudioManager#getDeviceVolumeBehavior} that takes advantage of cached
|
||||||
|
* results for the volume behaviors of HDMI audio devices.
|
||||||
|
*/
|
||||||
|
@AudioManager.DeviceVolumeBehavior
|
||||||
|
private int getDeviceVolumeBehavior(AudioDeviceAttributes device) {
|
||||||
|
if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mAudioDeviceVolumeBehaviors.containsKey(device)) {
|
||||||
|
return mAudioDeviceVolumeBehaviors.get(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getAudioManager().getDeviceVolumeBehavior(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether Absolute Volume Control is enabled or not. This is determined by the
|
||||||
|
* volume behavior of the relevant HDMI audio output device(s) for this device's type.
|
||||||
|
*/
|
||||||
|
public boolean isAbsoluteVolumeControlEnabled() {
|
||||||
|
if (!isTvDevice() && !isPlaybackDevice()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AudioDeviceAttributes avcAudioOutputDevice = getAvcAudioOutputDevice();
|
||||||
|
if (avcAudioOutputDevice == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getDeviceVolumeBehavior(avcAudioOutputDevice)
|
||||||
|
== AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AudioDeviceAttributes getAvcAudioOutputDevice() {
|
||||||
|
if (isTvDevice()) {
|
||||||
|
return tv().getSystemAudioOutputDevice();
|
||||||
|
} else if (isPlaybackDevice()) {
|
||||||
|
return AUDIO_OUTPUT_DEVICE_HDMI;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the conditions for Absolute Volume Control (AVC), and enables or disables the feature
|
||||||
|
* if necessary. AVC is enabled precisely when a specific audio output device
|
||||||
|
* (HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs) is using absolute volume
|
||||||
|
* behavior.
|
||||||
|
*
|
||||||
|
* AVC must be enabled on a Playback device or TV precisely when it is playing
|
||||||
|
* audio on an external device (the System Audio device) that supports the feature.
|
||||||
|
* This reduces to these conditions:
|
||||||
|
*
|
||||||
|
* 1. If the System Audio Device is an Audio System: System Audio Mode is active
|
||||||
|
* 2. Our HDMI audio output device is using full volume behavior
|
||||||
|
* 3. CEC volume is enabled
|
||||||
|
* 4. The System Audio device supports AVC (i.e. it supports <Set Audio Volume Level>)
|
||||||
|
*
|
||||||
|
* If not all of these conditions are met, this method disables AVC if necessary.
|
||||||
|
*
|
||||||
|
* If all of these conditions are met, this method starts an action to query the System Audio
|
||||||
|
* device's audio status, which enables AVC upon obtaining the audio status.
|
||||||
|
*/
|
||||||
|
@ServiceThreadOnly
|
||||||
|
void checkAndUpdateAbsoluteVolumeControlState() {
|
||||||
|
assertRunOnServiceThread();
|
||||||
|
|
||||||
|
// Can't enable or disable AVC before we have access to system services
|
||||||
|
if (getAudioManager() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HdmiCecLocalDevice localCecDevice;
|
||||||
|
if (isTvDevice() && tv() != null) {
|
||||||
|
localCecDevice = tv();
|
||||||
|
// Condition 1: TVs need System Audio Mode to be active
|
||||||
|
// (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
|
||||||
|
// TV is the System Audio Device instead.)
|
||||||
|
if (!isSystemAudioActivated()) {
|
||||||
|
disableAbsoluteVolumeControl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (isPlaybackDevice() && playback() != null) {
|
||||||
|
localCecDevice = playback();
|
||||||
|
} else {
|
||||||
|
// Either this device type doesn't support AVC, or it hasn't fully initialized yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HdmiDeviceInfo systemAudioDeviceInfo = getHdmiCecNetwork().getSafeCecDeviceInfo(
|
||||||
|
localCecDevice.findAudioReceiverAddress());
|
||||||
|
@AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
|
||||||
|
getDeviceVolumeBehavior(getAvcAudioOutputDevice());
|
||||||
|
|
||||||
|
// Condition 2: Already using full or absolute volume behavior
|
||||||
|
boolean alreadyUsingFullOrAbsoluteVolume =
|
||||||
|
currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL
|
||||||
|
|| currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
|
||||||
|
// Condition 3: CEC volume is enabled
|
||||||
|
boolean cecVolumeEnabled =
|
||||||
|
getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
|
||||||
|
|
||||||
|
if (!cecVolumeEnabled || !alreadyUsingFullOrAbsoluteVolume) {
|
||||||
|
disableAbsoluteVolumeControl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for safety: if the System Audio device is a candidate for AVC, we should already
|
||||||
|
// have received messages from it to trigger the other conditions.
|
||||||
|
if (systemAudioDeviceInfo == null) {
|
||||||
|
disableAbsoluteVolumeControl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Condition 4: The System Audio device supports AVC (i.e. <Set Audio Volume Level>).
|
||||||
|
switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
|
||||||
|
case DeviceFeatures.FEATURE_SUPPORTED:
|
||||||
|
if (!isAbsoluteVolumeControlEnabled()) {
|
||||||
|
// Start an action that will call {@link #enableAbsoluteVolumeControl}
|
||||||
|
// once the System Audio device sends <Report Audio Status>
|
||||||
|
localCecDevice.addAvcAudioStatusAction(
|
||||||
|
systemAudioDeviceInfo.getLogicalAddress());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case DeviceFeatures.FEATURE_NOT_SUPPORTED:
|
||||||
|
disableAbsoluteVolumeControl();
|
||||||
|
return;
|
||||||
|
case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN:
|
||||||
|
disableAbsoluteVolumeControl();
|
||||||
|
localCecDevice.queryAvcSupport(systemAudioDeviceInfo.getLogicalAddress());
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableAbsoluteVolumeControl() {
|
||||||
|
if (isPlaybackDevice()) {
|
||||||
|
playback().removeAvcAudioStatusAction();
|
||||||
|
} else if (isTvDevice()) {
|
||||||
|
tv().removeAvcAudioStatusAction();
|
||||||
|
}
|
||||||
|
AudioDeviceAttributes device = getAvcAudioOutputDevice();
|
||||||
|
if (getDeviceVolumeBehavior(device) == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
|
||||||
|
getAudioManager().setDeviceVolumeBehavior(device,
|
||||||
|
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables Absolute Volume Control. Should only be called when all the conditions for
|
||||||
|
* AVC are met (see {@link #checkAndUpdateAbsoluteVolumeControlState}).
|
||||||
|
* @param audioStatus The initial audio status to set the audio output device to
|
||||||
|
*/
|
||||||
|
void enableAbsoluteVolumeControl(AudioStatus audioStatus) {
|
||||||
|
HdmiCecLocalDevice localDevice = isPlaybackDevice() ? playback() : tv();
|
||||||
|
HdmiDeviceInfo systemAudioDevice = getHdmiCecNetwork().getDeviceInfo(
|
||||||
|
localDevice.findAudioReceiverAddress());
|
||||||
|
VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
|
||||||
|
.setMuted(audioStatus.getMute())
|
||||||
|
.setVolumeIndex(audioStatus.getVolume())
|
||||||
|
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
|
||||||
|
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
|
||||||
|
.build();
|
||||||
|
mAbsoluteVolumeChangedListener = new AbsoluteVolumeChangedListener(
|
||||||
|
localDevice, systemAudioDevice);
|
||||||
|
|
||||||
|
// AudioService sets the volume of the stream and device based on the input VolumeInfo
|
||||||
|
// when enabling absolute volume behavior, but not the mute state
|
||||||
|
notifyAvcMuteChange(audioStatus.getMute());
|
||||||
|
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
|
||||||
|
getAvcAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
|
||||||
|
mAbsoluteVolumeChangedListener, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbsoluteVolumeChangedListener mAbsoluteVolumeChangedListener;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
AbsoluteVolumeChangedListener getAbsoluteVolumeChangedListener() {
|
||||||
|
return mAbsoluteVolumeChangedListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listeners for changes reported by AudioService to the state of an audio output device using
|
||||||
|
* absolute volume behavior.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
class AbsoluteVolumeChangedListener implements
|
||||||
|
AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener {
|
||||||
|
private HdmiCecLocalDevice mLocalDevice;
|
||||||
|
private HdmiDeviceInfo mSystemAudioDevice;
|
||||||
|
|
||||||
|
private AbsoluteVolumeChangedListener(HdmiCecLocalDevice localDevice,
|
||||||
|
HdmiDeviceInfo systemAudioDevice) {
|
||||||
|
mLocalDevice = localDevice;
|
||||||
|
mSystemAudioDevice = systemAudioDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when AudioService sets the volume level of an absolute volume audio output device
|
||||||
|
* to a numeric value.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onAudioDeviceVolumeChanged(
|
||||||
|
@NonNull AudioDeviceAttributes audioDevice,
|
||||||
|
@NonNull VolumeInfo volumeInfo) {
|
||||||
|
int localDeviceAddress;
|
||||||
|
synchronized (mLocalDevice.mLock) {
|
||||||
|
localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
|
||||||
|
}
|
||||||
|
sendCecCommand(SetAudioVolumeLevelMessage.build(
|
||||||
|
localDeviceAddress,
|
||||||
|
mSystemAudioDevice.getLogicalAddress(),
|
||||||
|
volumeInfo.getVolumeIndex()),
|
||||||
|
// If sending the message fails, ask the System Audio device for its
|
||||||
|
// audio status so that we can update AudioService
|
||||||
|
(int errorCode) -> {
|
||||||
|
if (errorCode == SendMessageResult.SUCCESS) {
|
||||||
|
// Update the volume tracked in our AbsoluteVolumeAudioStatusAction
|
||||||
|
// so it correctly processes incoming <Report Audio Status> messages
|
||||||
|
HdmiCecLocalDevice avcDevice = isTvDevice() ? tv() : playback();
|
||||||
|
avcDevice.updateAvcVolume(volumeInfo.getVolumeIndex());
|
||||||
|
} else {
|
||||||
|
sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
|
||||||
|
localDeviceAddress,
|
||||||
|
mSystemAudioDevice.getLogicalAddress()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when AudioService adjusts the volume or mute state of an absolute volume
|
||||||
|
* audio output device
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onAudioDeviceVolumeAdjusted(
|
||||||
|
@NonNull AudioDeviceAttributes audioDevice,
|
||||||
|
@NonNull VolumeInfo volumeInfo,
|
||||||
|
@AudioManager.VolumeAdjustment int direction,
|
||||||
|
@AudioDeviceVolumeManager.VolumeAdjustmentMode int mode
|
||||||
|
) {
|
||||||
|
int keyCode;
|
||||||
|
switch (direction) {
|
||||||
|
case AudioManager.ADJUST_RAISE:
|
||||||
|
keyCode = KeyEvent.KEYCODE_VOLUME_UP;
|
||||||
|
break;
|
||||||
|
case AudioManager.ADJUST_LOWER:
|
||||||
|
keyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
|
||||||
|
break;
|
||||||
|
case AudioManager.ADJUST_TOGGLE_MUTE:
|
||||||
|
case AudioManager.ADJUST_MUTE:
|
||||||
|
case AudioManager.ADJUST_UNMUTE:
|
||||||
|
// Many CEC devices only support toggle mute. Therefore, we send the
|
||||||
|
// same keycode for all three mute options.
|
||||||
|
keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (mode) {
|
||||||
|
case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL:
|
||||||
|
mLocalDevice.sendVolumeKeyEvent(keyCode, true);
|
||||||
|
mLocalDevice.sendVolumeKeyEvent(keyCode, false);
|
||||||
|
break;
|
||||||
|
case AudioDeviceVolumeManager.ADJUST_MODE_START:
|
||||||
|
mLocalDevice.sendVolumeKeyEvent(keyCode, true);
|
||||||
|
break;
|
||||||
|
case AudioDeviceVolumeManager.ADJUST_MODE_END:
|
||||||
|
mLocalDevice.sendVolumeKeyEvent(keyCode, false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies AudioService of a change in the volume of the System Audio device. Has no effect if
|
||||||
|
* AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
|
||||||
|
*/
|
||||||
|
void notifyAvcVolumeChange(int volume) {
|
||||||
|
if (!isAbsoluteVolumeControlEnabled()) return;
|
||||||
|
List<AudioDeviceAttributes> streamMusicDevices =
|
||||||
|
getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
|
||||||
|
if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
|
||||||
|
setStreamMusicVolume(volume, AudioManager.FLAG_ABSOLUTE_VOLUME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies AudioService of a change in the mute status of the System Audio device. Has no
|
||||||
|
* effect if AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
|
||||||
|
*/
|
||||||
|
void notifyAvcMuteChange(boolean mute) {
|
||||||
|
if (!isAbsoluteVolumeControlEnabled()) return;
|
||||||
|
List<AudioDeviceAttributes> streamMusicDevices =
|
||||||
|
getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
|
||||||
|
if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
|
||||||
|
int direction = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE;
|
||||||
|
getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction,
|
||||||
|
AudioManager.FLAG_ABSOLUTE_VOLUME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the volume index of {@link AudioManager#STREAM_MUSIC}. Rescales the input volume index
|
||||||
|
* from HDMI-CEC volume range to STREAM_MUSIC's.
|
||||||
|
*/
|
||||||
|
void setStreamMusicVolume(int volume, int flags) {
|
||||||
|
getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC,
|
||||||
|
volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, flags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,8 +172,19 @@ final class SendKeyAction extends HdmiCecFeatureAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendKeyUp() {
|
private void sendKeyUp() {
|
||||||
sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
|
// When using Absolute Volume Control, query audio status after a volume key is released.
|
||||||
mTargetAddress));
|
// This allows us to notify AudioService of the resulting volume or mute status changes.
|
||||||
|
if (HdmiCecKeycode.isVolumeKeycode(mLastKeycode)
|
||||||
|
&& localDevice().getService().isAbsoluteVolumeControlEnabled()) {
|
||||||
|
sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
|
||||||
|
mTargetAddress),
|
||||||
|
__ -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
|
||||||
|
getSourceAddress(),
|
||||||
|
localDevice().findAudioReceiverAddress())));
|
||||||
|
} else {
|
||||||
|
sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
|
||||||
|
mTargetAddress));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -122,4 +122,11 @@ public class SetAudioVolumeLevelDiscoveryAction extends HdmiCecFeatureAction {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the logical address of this action's target device.
|
||||||
|
*/
|
||||||
|
public int getTargetAddress() {
|
||||||
|
return mTargetAddress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction {
|
|||||||
boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
|
boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
|
||||||
if (receivedStatus == mTargetAudioStatus) {
|
if (receivedStatus == mTargetAudioStatus) {
|
||||||
setSystemAudioMode(receivedStatus);
|
setSystemAudioMode(receivedStatus);
|
||||||
startAudioStatusAction();
|
finish();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus);
|
HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus);
|
||||||
@@ -168,11 +168,6 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void startAudioStatusAction() {
|
|
||||||
addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress, mCallbacks));
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void removeSystemAudioActionInProgress() {
|
protected void removeSystemAudioActionInProgress() {
|
||||||
removeActionExcept(SystemAudioActionFromTv.class, this);
|
removeActionExcept(SystemAudioActionFromTv.class, this);
|
||||||
removeActionExcept(SystemAudioActionFromAvr.class, this);
|
removeActionExcept(SystemAudioActionFromAvr.class, this);
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
package com.android.server.hdmi;
|
package com.android.server.hdmi;
|
||||||
|
|
||||||
import android.hardware.hdmi.HdmiDeviceInfo;
|
|
||||||
import android.hardware.hdmi.HdmiControlManager;
|
import android.hardware.hdmi.HdmiControlManager;
|
||||||
|
import android.hardware.hdmi.HdmiDeviceInfo;
|
||||||
import android.hardware.hdmi.IHdmiControlCallback;
|
import android.hardware.hdmi.IHdmiControlCallback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +65,7 @@ final class SystemAudioActionFromAvr extends SystemAudioAction {
|
|||||||
|
|
||||||
if (mTargetAudioStatus) {
|
if (mTargetAudioStatus) {
|
||||||
setSystemAudioMode(true);
|
setSystemAudioMode(true);
|
||||||
startAudioStatusAction();
|
finish();
|
||||||
} else {
|
} else {
|
||||||
setSystemAudioMode(false);
|
setSystemAudioMode(false);
|
||||||
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
|
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 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.hardware.hdmi.HdmiControlManager;
|
|
||||||
import android.hardware.hdmi.IHdmiControlCallback;
|
|
||||||
import android.hardware.tv.cec.V1_0.SendMessageResult;
|
|
||||||
|
|
||||||
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action to update audio status (volume or mute) of audio amplifier
|
|
||||||
*/
|
|
||||||
final class SystemAudioStatusAction extends HdmiCecFeatureAction {
|
|
||||||
private static final String TAG = "SystemAudioStatusAction";
|
|
||||||
|
|
||||||
// State that waits for <ReportAudioStatus>.
|
|
||||||
private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 1;
|
|
||||||
|
|
||||||
private final int mAvrAddress;
|
|
||||||
|
|
||||||
SystemAudioStatusAction(
|
|
||||||
HdmiCecLocalDevice source, int avrAddress, List<IHdmiControlCallback> callbacks) {
|
|
||||||
super(source, callbacks);
|
|
||||||
mAvrAddress = avrAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemAudioStatusAction(HdmiCecLocalDevice source, int avrAddress,
|
|
||||||
IHdmiControlCallback callback) {
|
|
||||||
super(source, callback);
|
|
||||||
mAvrAddress = avrAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean start() {
|
|
||||||
mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
|
|
||||||
addTimer(mState, HdmiConfig.TIMEOUT_MS);
|
|
||||||
sendGiveAudioStatus();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendGiveAudioStatus() {
|
|
||||||
sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mAvrAddress),
|
|
||||||
new SendMessageCallback() {
|
|
||||||
@Override
|
|
||||||
public void onSendCompleted(int error) {
|
|
||||||
if (error != SendMessageResult.SUCCESS) {
|
|
||||||
handleSendGiveAudioStatusFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSendGiveAudioStatusFailure() {
|
|
||||||
|
|
||||||
// Still return SUCCESS to callback.
|
|
||||||
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean processCommand(HdmiCecMessage cmd) {
|
|
||||||
if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS || mAvrAddress != cmd.getSource()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (cmd.getOpcode()) {
|
|
||||||
case Constants.MESSAGE_REPORT_AUDIO_STATUS:
|
|
||||||
handleReportAudioStatus(cmd);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleReportAudioStatus(HdmiCecMessage cmd) {
|
|
||||||
byte[] params = cmd.getParams();
|
|
||||||
boolean mute = HdmiUtils.isAudioStatusMute(cmd);
|
|
||||||
int volume = HdmiUtils.getAudioStatusVolume(cmd);
|
|
||||||
tv().setAudioStatus(mute, volume);
|
|
||||||
|
|
||||||
if (!(tv().isSystemAudioActivated() ^ mute)) {
|
|
||||||
// Toggle AVR's mute status to match with the system audio status.
|
|
||||||
sendUserControlPressedAndReleased(mAvrAddress, HdmiCecKeycode.CEC_KEYCODE_MUTE);
|
|
||||||
}
|
|
||||||
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void handleTimerEvent(int state) {
|
|
||||||
if (mState != state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSendGiveAudioStatusFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -61,7 +61,8 @@ public class ActiveSourceActionTest {
|
|||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||||
|
|
||||||
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
|
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
AudioManager getAudioManager() {
|
AudioManager getAudioManager() {
|
||||||
return new AudioManager() {
|
return new AudioManager() {
|
||||||
@@ -93,6 +94,7 @@ public class ActiveSourceActionTest {
|
|||||||
mHdmiControlService.setCecController(hdmiCecController);
|
mHdmiControlService.setCecController(hdmiCecController);
|
||||||
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
|
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mPhysicalAddress = 0x2000;
|
mPhysicalAddress = 0x2000;
|
||||||
@@ -153,7 +155,6 @@ public class ActiveSourceActionTest {
|
|||||||
mHdmiControlService);
|
mHdmiControlService);
|
||||||
audioDevice.init();
|
audioDevice.init();
|
||||||
mLocalDevices.add(audioDevice);
|
mLocalDevices.add(audioDevice);
|
||||||
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
|
||||||
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
||||||
mTestLooper.dispatchAll();
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ public class ArcInitiationActionFromAvrTest {
|
|||||||
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||||
|
|
||||||
HdmiControlService hdmiControlService =
|
HdmiControlService hdmiControlService =
|
||||||
new HdmiControlService(mContextSpy, Collections.emptyList()) {
|
new HdmiControlService(mContextSpy, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
boolean isPowerStandby() {
|
boolean isPowerStandby() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ public class ArcTerminationActionFromAvrTest {
|
|||||||
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||||
|
|
||||||
HdmiControlService hdmiControlService =
|
HdmiControlService hdmiControlService =
|
||||||
new HdmiControlService(mContextSpy, Collections.emptyList()) {
|
new HdmiControlService(mContextSpy, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
AudioManager getAudioManager() {
|
AudioManager getAudioManager() {
|
||||||
return mAudioManager;
|
return mAudioManager;
|
||||||
|
|||||||
@@ -0,0 +1,570 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
|
||||||
|
|
||||||
|
import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP;
|
||||||
|
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.common.truth.TruthJUnit.assume;
|
||||||
|
|
||||||
|
import static org.mockito.AdditionalMatchers.not;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.clearInvocations;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.eq;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
import android.hardware.hdmi.DeviceFeatures;
|
||||||
|
import android.hardware.hdmi.HdmiControlManager;
|
||||||
|
import android.hardware.hdmi.HdmiDeviceInfo;
|
||||||
|
import android.hardware.hdmi.HdmiPortInfo;
|
||||||
|
import android.hardware.tv.cec.V1_0.SendMessageResult;
|
||||||
|
import android.media.AudioDeviceAttributes;
|
||||||
|
import android.media.AudioDeviceVolumeManager;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.media.VolumeInfo;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.os.test.TestLooper;
|
||||||
|
|
||||||
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
|
||||||
|
import com.android.server.SystemService;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that Absolute Volume Control (AVC) is enabled and disabled correctly, and that
|
||||||
|
* the device responds correctly to incoming <Report Audio Status> messages and API calls
|
||||||
|
* from AudioService when AVC is active.
|
||||||
|
*
|
||||||
|
* This is an abstract base class. Concrete subclasses specify the type of the local device, and the
|
||||||
|
* type of the System Audio device. This allows each test to be run for multiple setups.
|
||||||
|
*
|
||||||
|
* We test the following pairs of (local device, System Audio device):
|
||||||
|
* (Playback, TV): {@link PlaybackDeviceToTvAvcTest}
|
||||||
|
* (Playback, Audio System): {@link PlaybackDeviceToAudioSystemAvcTest}
|
||||||
|
* (TV, Audio System): {@link TvToAudioSystemAvcTest}
|
||||||
|
*/
|
||||||
|
public abstract class BaseAbsoluteVolumeControlTest {
|
||||||
|
private HdmiControlService mHdmiControlService;
|
||||||
|
private HdmiCecController mHdmiCecController;
|
||||||
|
private HdmiCecLocalDevice mHdmiCecLocalDevice;
|
||||||
|
private FakeHdmiCecConfig mHdmiCecConfig;
|
||||||
|
private FakePowerManagerWrapper mPowerManager;
|
||||||
|
private Looper mLooper;
|
||||||
|
private Context mContextSpy;
|
||||||
|
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
|
||||||
|
|
||||||
|
@Mock protected AudioManager mAudioManager;
|
||||||
|
protected FakeAudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager;
|
||||||
|
|
||||||
|
protected TestLooper mTestLooper = new TestLooper();
|
||||||
|
protected FakeNativeWrapper mNativeWrapper;
|
||||||
|
|
||||||
|
// Audio Status given by the System Audio device in its initial <Report Audio Status> that
|
||||||
|
// triggers AVC being enabled
|
||||||
|
private static final AudioStatus INITIAL_SYSTEM_AUDIO_DEVICE_STATUS =
|
||||||
|
new AudioStatus(50, false);
|
||||||
|
|
||||||
|
// VolumeInfo passed to AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior to enable AVC
|
||||||
|
private static final VolumeInfo ENABLE_AVC_VOLUME_INFO =
|
||||||
|
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
|
||||||
|
.setMuted(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute())
|
||||||
|
.setVolumeIndex(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume())
|
||||||
|
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
|
||||||
|
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
protected abstract HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService);
|
||||||
|
|
||||||
|
protected abstract int getPhysicalAddress();
|
||||||
|
protected abstract int getDeviceType();
|
||||||
|
protected abstract AudioDeviceAttributes getAudioOutputDevice();
|
||||||
|
|
||||||
|
protected abstract int getSystemAudioDeviceLogicalAddress();
|
||||||
|
protected abstract int getSystemAudioDeviceType();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws RemoteException {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
mContextSpy = spy(new ContextWrapper(
|
||||||
|
InstrumentationRegistry.getInstrumentation().getTargetContext()));
|
||||||
|
|
||||||
|
mAudioDeviceVolumeManager = spy(new FakeAudioDeviceVolumeManagerWrapper());
|
||||||
|
|
||||||
|
mHdmiControlService =
|
||||||
|
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
||||||
|
Collections.singletonList(getDeviceType()),
|
||||||
|
mAudioDeviceVolumeManager) {
|
||||||
|
@Override
|
||||||
|
AudioManager getAudioManager() {
|
||||||
|
return mAudioManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeStringSystemProperty(String key, String value) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mLooper = mTestLooper.getLooper();
|
||||||
|
mHdmiControlService.setIoLooper(mLooper);
|
||||||
|
|
||||||
|
mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
|
||||||
|
mHdmiCecConfig.setIntValue(
|
||||||
|
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
|
||||||
|
HdmiControlManager.HDMI_CEC_VERSION_2_0);
|
||||||
|
mHdmiCecConfig.setIntValue(
|
||||||
|
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
|
||||||
|
HdmiControlManager.VOLUME_CONTROL_DISABLED);
|
||||||
|
mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig);
|
||||||
|
|
||||||
|
mNativeWrapper = new FakeNativeWrapper();
|
||||||
|
mNativeWrapper.setPhysicalAddress(getPhysicalAddress());
|
||||||
|
mNativeWrapper.setPollAddressResponse(Constants.ADDR_TV, SendMessageResult.SUCCESS);
|
||||||
|
|
||||||
|
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
|
||||||
|
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
|
||||||
|
mHdmiControlService.setCecController(mHdmiCecController);
|
||||||
|
mHdmiControlService.setHdmiMhlController(
|
||||||
|
HdmiMhlControllerStub.create(mHdmiControlService));
|
||||||
|
mHdmiControlService.initService();
|
||||||
|
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
||||||
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
|
|
||||||
|
mHdmiCecLocalDevice = createLocalDevice(mHdmiControlService);
|
||||||
|
mHdmiCecLocalDevice.init();
|
||||||
|
mLocalDevices.add(mHdmiCecLocalDevice);
|
||||||
|
|
||||||
|
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
|
||||||
|
hdmiPortInfos[0] =
|
||||||
|
new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
|
||||||
|
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
||||||
|
mNativeWrapper.setPortConnectionStatus(1, true);
|
||||||
|
|
||||||
|
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_BOOT_UP);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
// Simulate AudioManager's behavior and response when setDeviceVolumeBehavior is called
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
setDeviceVolumeBehavior(invocation.getArgument(0), invocation.getArgument(1));
|
||||||
|
return null;
|
||||||
|
}).when(mAudioManager).setDeviceVolumeBehavior(any(), anyInt());
|
||||||
|
|
||||||
|
// Set starting volume behavior
|
||||||
|
doReturn(AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE)
|
||||||
|
.when(mAudioManager).getDeviceVolumeBehavior(eq(getAudioOutputDevice()));
|
||||||
|
|
||||||
|
// Audio service always plays STREAM_MUSIC on the device we need
|
||||||
|
doReturn(Collections.singletonList(getAudioOutputDevice())).when(mAudioManager)
|
||||||
|
.getDevicesForAttributes(HdmiControlService.STREAM_MUSIC_ATTRIBUTES);
|
||||||
|
|
||||||
|
// Max volume of STREAM_MUSIC
|
||||||
|
doReturn(25).when(mAudioManager).getStreamMaxVolume(AudioManager.STREAM_MUSIC);
|
||||||
|
|
||||||
|
// Receive messages from devices to make sure they're registered in HdmiCecNetwork
|
||||||
|
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
|
||||||
|
Constants.ADDR_TV, getLogicalAddress()));
|
||||||
|
if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) {
|
||||||
|
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
|
||||||
|
Constants.ADDR_AUDIO_SYSTEM, getLogicalAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
|
||||||
|
mHdmiControlService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getLogicalAddress() {
|
||||||
|
synchronized (mHdmiCecLocalDevice.mLock) {
|
||||||
|
return mHdmiCecLocalDevice.getDeviceInfo().getLogicalAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates the volume behavior of {@code device} being set to {@code behavior}.
|
||||||
|
*/
|
||||||
|
protected void setDeviceVolumeBehavior(AudioDeviceAttributes device,
|
||||||
|
@AudioManager.DeviceVolumeBehavior int behavior) {
|
||||||
|
doReturn(behavior).when(mAudioManager).getDeviceVolumeBehavior(eq(device));
|
||||||
|
mHdmiControlService.onDeviceVolumeBehaviorChanged(device, behavior);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the setting for CEC volume.
|
||||||
|
*/
|
||||||
|
protected void setCecVolumeControlSetting(@HdmiControlManager.VolumeControl int setting) {
|
||||||
|
mHdmiControlService.getHdmiCecConfig().setIntValue(
|
||||||
|
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, setting);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the device receive a <Report Features> message from the System Audio device specifying
|
||||||
|
* whether <Set Audio Volume Level> is supported or not.
|
||||||
|
*/
|
||||||
|
protected void receiveSetAudioVolumeLevelSupport(
|
||||||
|
@DeviceFeatures.FeatureSupportStatus int featureSupportStatus) {
|
||||||
|
// <Report Features> can't specify an unknown feature support status
|
||||||
|
if (featureSupportStatus != DeviceFeatures.FEATURE_SUPPORT_UNKNOWN) {
|
||||||
|
mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
|
||||||
|
getSystemAudioDeviceLogicalAddress(), HdmiControlManager.HDMI_CEC_VERSION_2_0,
|
||||||
|
Arrays.asList(getSystemAudioDeviceType()), Constants.RC_PROFILE_SOURCE,
|
||||||
|
Collections.emptyList(),
|
||||||
|
DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
|
||||||
|
.setSetAudioVolumeLevelSupport(featureSupportStatus)
|
||||||
|
.build()));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables System Audio mode if the System Audio device is an Audio System.
|
||||||
|
*/
|
||||||
|
protected void enableSystemAudioModeIfNeeded() {
|
||||||
|
if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) {
|
||||||
|
receiveSetSystemAudioMode(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets System Audio Mode by having the device receive <Set System Audio Mode>
|
||||||
|
* from the Audio System.
|
||||||
|
*/
|
||||||
|
protected void receiveSetSystemAudioMode(boolean status) {
|
||||||
|
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
|
||||||
|
Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, status));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the device receive a <Report Audio Status> reporting the status in
|
||||||
|
* {@link #INITIAL_SYSTEM_AUDIO_DEVICE_STATUS}
|
||||||
|
*/
|
||||||
|
protected void receiveInitialReportAudioStatus() {
|
||||||
|
receiveReportAudioStatus(
|
||||||
|
INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
|
||||||
|
INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the device receive a <Report Audio Status> message from the System Audio Device.
|
||||||
|
*/
|
||||||
|
protected void receiveReportAudioStatus(int volume, boolean mute) {
|
||||||
|
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
|
||||||
|
getSystemAudioDeviceLogicalAddress(),
|
||||||
|
getLogicalAddress(),
|
||||||
|
volume,
|
||||||
|
mute));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers all the conditions required to enable Absolute Volume Control.
|
||||||
|
*/
|
||||||
|
protected void enableAbsoluteVolumeControl() {
|
||||||
|
setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
|
||||||
|
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
|
||||||
|
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
|
||||||
|
enableSystemAudioModeIfNeeded();
|
||||||
|
receiveInitialReportAudioStatus();
|
||||||
|
|
||||||
|
verifyAbsoluteVolumeEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that AVC was enabled - that is the audio output device's volume behavior was last
|
||||||
|
* set to absolute volume behavior.
|
||||||
|
*/
|
||||||
|
protected void verifyAbsoluteVolumeEnabled() {
|
||||||
|
InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
|
||||||
|
inOrder.verify(mAudioDeviceVolumeManager, atLeastOnce()).setDeviceAbsoluteVolumeBehavior(
|
||||||
|
eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
|
||||||
|
inOrder.verify(mAudioManager, never()).setDeviceVolumeBehavior(
|
||||||
|
eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that AVC was disabled - that is, the audio output device's volume behavior was
|
||||||
|
* last set to something other than absolute volume behavior.
|
||||||
|
*/
|
||||||
|
protected void verifyAbsoluteVolumeDisabled() {
|
||||||
|
InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
|
||||||
|
inOrder.verify(mAudioManager, atLeastOnce()).setDeviceVolumeBehavior(
|
||||||
|
eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
|
||||||
|
inOrder.verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
|
||||||
|
eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void verifyGiveAudioStatusNeverSent() {
|
||||||
|
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
|
||||||
|
HdmiCecMessageBuilder.buildGiveAudioStatus(
|
||||||
|
getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void verifyGiveAudioStatusSent() {
|
||||||
|
assertThat(mNativeWrapper.getResultMessages()).contains(
|
||||||
|
HdmiCecMessageBuilder.buildGiveAudioStatus(
|
||||||
|
getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allConditionsExceptSavlSupportMet_sendsSetAudioVolumeLevelAndGiveFeatures() {
|
||||||
|
setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
|
||||||
|
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
|
||||||
|
enableSystemAudioModeIfNeeded();
|
||||||
|
|
||||||
|
assertThat(mNativeWrapper.getResultMessages()).contains(
|
||||||
|
SetAudioVolumeLevelMessage.build(
|
||||||
|
getLogicalAddress(), getSystemAudioDeviceLogicalAddress(),
|
||||||
|
Constants.AUDIO_VOLUME_STATUS_UNKNOWN));
|
||||||
|
assertThat(mNativeWrapper.getResultMessages()).contains(
|
||||||
|
HdmiCecMessageBuilder.buildGiveFeatures(
|
||||||
|
getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allConditionsMet_savlSupportLast_reportFeatures_giveAudioStatusSent() {
|
||||||
|
setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
|
||||||
|
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
|
||||||
|
enableSystemAudioModeIfNeeded();
|
||||||
|
verifyGiveAudioStatusNeverSent();
|
||||||
|
|
||||||
|
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
|
||||||
|
verifyGiveAudioStatusSent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allConditionsMet_savlSupportLast_noFeatureAbort_giveAudioStatusSent() {
|
||||||
|
setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
|
||||||
|
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
|
||||||
|
enableSystemAudioModeIfNeeded();
|
||||||
|
verifyGiveAudioStatusNeverSent();
|
||||||
|
|
||||||
|
mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
verifyGiveAudioStatusSent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allConditionsMet_cecVolumeEnabledLast_giveAudioStatusSent() {
|
||||||
|
setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
|
||||||
|
enableSystemAudioModeIfNeeded();
|
||||||
|
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
|
||||||
|
verifyGiveAudioStatusNeverSent();
|
||||||
|
|
||||||
|
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
|
||||||
|
verifyGiveAudioStatusSent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allConditionsMet_fullVolumeBehaviorLast_giveAudioStatusSent() {
|
||||||
|
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
|
||||||
|
enableSystemAudioModeIfNeeded();
|
||||||
|
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
|
||||||
|
verifyGiveAudioStatusNeverSent();
|
||||||
|
|
||||||
|
setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
|
||||||
|
verifyGiveAudioStatusSent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allConditionsMet_systemAudioModeEnabledLast_giveAudioStatusSent() {
|
||||||
|
// Only run when the System Audio device is an Audio System.
|
||||||
|
assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
|
||||||
|
|
||||||
|
setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
|
||||||
|
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
|
||||||
|
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
|
||||||
|
verifyGiveAudioStatusNeverSent();
|
||||||
|
|
||||||
|
receiveSetSystemAudioMode(true);
|
||||||
|
verifyGiveAudioStatusSent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void giveAudioStatusSent_systemAudioDeviceSendsReportAudioStatus_avcEnabled() {
|
||||||
|
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
|
||||||
|
enableSystemAudioModeIfNeeded();
|
||||||
|
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
|
||||||
|
setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
|
||||||
|
|
||||||
|
// Verify that AVC was never enabled
|
||||||
|
verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
|
||||||
|
eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
|
||||||
|
receiveInitialReportAudioStatus();
|
||||||
|
|
||||||
|
verifyAbsoluteVolumeEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void avcEnabled_cecVolumeDisabled_absoluteVolumeDisabled() {
|
||||||
|
enableAbsoluteVolumeControl();
|
||||||
|
|
||||||
|
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_DISABLED);
|
||||||
|
verifyAbsoluteVolumeDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void avcEnabled_setAudioVolumeLevelNotSupported_absoluteVolumeDisabled() {
|
||||||
|
enableAbsoluteVolumeControl();
|
||||||
|
|
||||||
|
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
|
||||||
|
verifyAbsoluteVolumeDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void avcEnabled_setAudioVolumeLevelFeatureAborted_absoluteVolumeDisabled() {
|
||||||
|
enableAbsoluteVolumeControl();
|
||||||
|
|
||||||
|
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
|
||||||
|
getSystemAudioDeviceLogicalAddress(), getLogicalAddress(),
|
||||||
|
Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
verifyAbsoluteVolumeDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void avcEnabled_systemAudioModeDisabled_absoluteVolumeDisabled() {
|
||||||
|
// Only run when the System Audio device is an Audio System.
|
||||||
|
assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
|
||||||
|
|
||||||
|
enableAbsoluteVolumeControl();
|
||||||
|
|
||||||
|
receiveSetSystemAudioMode(false);
|
||||||
|
verifyAbsoluteVolumeDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void avcEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() {
|
||||||
|
// Initial <Report Audio Status> has volume=50 and mute=false
|
||||||
|
enableAbsoluteVolumeControl();
|
||||||
|
|
||||||
|
// New volume and mute status: sets both
|
||||||
|
receiveReportAudioStatus(20, true);
|
||||||
|
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5),
|
||||||
|
anyInt());
|
||||||
|
verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
|
||||||
|
eq(AudioManager.ADJUST_MUTE), anyInt());
|
||||||
|
clearInvocations(mAudioManager);
|
||||||
|
|
||||||
|
// New volume only: sets volume only
|
||||||
|
receiveReportAudioStatus(32, true);
|
||||||
|
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
|
||||||
|
anyInt());
|
||||||
|
verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
|
||||||
|
eq(AudioManager.ADJUST_MUTE), anyInt());
|
||||||
|
clearInvocations(mAudioManager);
|
||||||
|
|
||||||
|
// New mute status only: sets mute only
|
||||||
|
receiveReportAudioStatus(32, false);
|
||||||
|
verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
|
||||||
|
anyInt());
|
||||||
|
verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
|
||||||
|
eq(AudioManager.ADJUST_UNMUTE), anyInt());
|
||||||
|
clearInvocations(mAudioManager);
|
||||||
|
|
||||||
|
// Repeat of earlier message: sets neither volume nor mute
|
||||||
|
receiveReportAudioStatus(32, false);
|
||||||
|
verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
|
||||||
|
anyInt());
|
||||||
|
verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
|
||||||
|
eq(AudioManager.ADJUST_UNMUTE), anyInt());
|
||||||
|
clearInvocations(mAudioManager);
|
||||||
|
|
||||||
|
// If AudioService causes us to send <Set Audio Volume Level>, the System Audio device's
|
||||||
|
// volume changes. Afterward, a duplicate of an earlier <Report Audio Status> should
|
||||||
|
// still cause us to call setStreamVolume()
|
||||||
|
mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
|
||||||
|
getAudioOutputDevice(),
|
||||||
|
new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
|
||||||
|
.setVolumeIndex(20)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
receiveReportAudioStatus(32, false);
|
||||||
|
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
|
||||||
|
anyInt());
|
||||||
|
verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
|
||||||
|
eq(AudioManager.ADJUST_UNMUTE), anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void avcEnabled_audioDeviceVolumeAdjusted_sendsUserControlPressedAndGiveAudioStatus() {
|
||||||
|
enableAbsoluteVolumeControl();
|
||||||
|
mNativeWrapper.clearResultMessages();
|
||||||
|
|
||||||
|
mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted(
|
||||||
|
getAudioOutputDevice(),
|
||||||
|
ENABLE_AVC_VOLUME_INFO,
|
||||||
|
AudioManager.ADJUST_RAISE,
|
||||||
|
AudioDeviceVolumeManager.ADJUST_MODE_NORMAL
|
||||||
|
);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
assertThat(mNativeWrapper.getResultMessages()).contains(
|
||||||
|
HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(),
|
||||||
|
getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP));
|
||||||
|
assertThat(mNativeWrapper.getResultMessages()).contains(
|
||||||
|
HdmiCecMessageBuilder.buildUserControlReleased(getLogicalAddress(),
|
||||||
|
getSystemAudioDeviceLogicalAddress()));
|
||||||
|
assertThat(mNativeWrapper.getResultMessages()).contains(
|
||||||
|
HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
|
||||||
|
getSystemAudioDeviceLogicalAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void avcEnabled_audioDeviceVolumeChanged_sendsSetAudioVolumeLevel() {
|
||||||
|
enableAbsoluteVolumeControl();
|
||||||
|
mNativeWrapper.clearResultMessages();
|
||||||
|
|
||||||
|
mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
|
||||||
|
getAudioOutputDevice(),
|
||||||
|
new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
|
||||||
|
.setVolumeIndex(20)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
assertThat(mNativeWrapper.getResultMessages()).contains(
|
||||||
|
SetAudioVolumeLevelMessage.build(getLogicalAddress(),
|
||||||
|
getSystemAudioDeviceLogicalAddress(), 20));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,7 +56,7 @@ public class DetectTvSystemAudioModeSupportActionTest {
|
|||||||
mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234);
|
mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234);
|
||||||
HdmiControlService hdmiControlService =
|
HdmiControlService hdmiControlService =
|
||||||
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
||||||
Collections.emptyList()) {
|
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void sendCecCommand(
|
void sendCecCommand(
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.server.hdmi;
|
package com.android.server.hdmi;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
|
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_TV;
|
import static com.android.server.hdmi.Constants.ADDR_TV;
|
||||||
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
||||||
@@ -78,7 +79,8 @@ public class DevicePowerStatusActionTest {
|
|||||||
|
|
||||||
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||||
|
|
||||||
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
|
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
AudioManager getAudioManager() {
|
AudioManager getAudioManager() {
|
||||||
return new AudioManager() {
|
return new AudioManager() {
|
||||||
@@ -110,6 +112,7 @@ public class DevicePowerStatusActionTest {
|
|||||||
mHdmiControlService.setCecController(hdmiCecController);
|
mHdmiControlService.setCecController(hdmiCecController);
|
||||||
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
|
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mPhysicalAddress = 0x2000;
|
mPhysicalAddress = 0x2000;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_ON;
|
|||||||
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
|
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
|
||||||
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
|
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
|
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
|
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
|
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
|
||||||
@@ -100,7 +101,7 @@ public class DeviceSelectActionFromPlaybackTest {
|
|||||||
|
|
||||||
mHdmiControlService =
|
mHdmiControlService =
|
||||||
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
||||||
Collections.emptyList()) {
|
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
boolean isControlEnabled() {
|
boolean isControlEnabled() {
|
||||||
return true;
|
return true;
|
||||||
@@ -136,6 +137,7 @@ public class DeviceSelectActionFromPlaybackTest {
|
|||||||
|
|
||||||
mLocalDevices.add(mHdmiCecLocalDevicePlayback);
|
mLocalDevices.add(mHdmiCecLocalDevicePlayback);
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
||||||
mNativeWrapper.setPhysicalAddress(0x0000);
|
mNativeWrapper.setPhysicalAddress(0x0000);
|
||||||
mPowerManager = new FakePowerManagerWrapper(context);
|
mPowerManager = new FakePowerManagerWrapper(context);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_ON;
|
|||||||
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
|
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
|
||||||
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
|
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
|
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
|
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_TV;
|
import static com.android.server.hdmi.Constants.ADDR_TV;
|
||||||
@@ -109,7 +110,7 @@ public class DeviceSelectActionFromTvTest {
|
|||||||
|
|
||||||
mHdmiControlService =
|
mHdmiControlService =
|
||||||
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
||||||
Collections.emptyList()) {
|
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
boolean isControlEnabled() {
|
boolean isControlEnabled() {
|
||||||
return true;
|
return true;
|
||||||
@@ -145,6 +146,7 @@ public class DeviceSelectActionFromTvTest {
|
|||||||
true, false, false);
|
true, false, false);
|
||||||
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(context);
|
mPowerManager = new FakePowerManagerWrapper(context);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,7 +89,8 @@ public class HdmiCecAtomLoggingTest {
|
|||||||
mContextSpy = spy(new ContextWrapper(
|
mContextSpy = spy(new ContextWrapper(
|
||||||
InstrumentationRegistry.getInstrumentation().getTargetContext()));
|
InstrumentationRegistry.getInstrumentation().getTargetContext()));
|
||||||
|
|
||||||
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
|
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()));
|
||||||
doNothing().when(mHdmiControlServiceSpy)
|
doNothing().when(mHdmiControlServiceSpy)
|
||||||
.writeStringSystemProperty(anyString(), anyString());
|
.writeStringSystemProperty(anyString(), anyString());
|
||||||
doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
|
doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
|
||||||
|
|||||||
@@ -101,7 +101,8 @@ public class HdmiCecControllerTest {
|
|||||||
mMyLooper = mTestLooper.getLooper();
|
mMyLooper = mTestLooper.getLooper();
|
||||||
|
|
||||||
mHdmiControlServiceSpy = spy(new HdmiControlService(
|
mHdmiControlServiceSpy = spy(new HdmiControlService(
|
||||||
InstrumentationRegistry.getTargetContext(), Collections.emptyList()));
|
InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()));
|
||||||
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper();
|
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper();
|
||||||
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper();
|
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper();
|
||||||
doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
|
doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public class HdmiCecLocalDeviceAudioSystemTest {
|
|||||||
|
|
||||||
mHdmiControlService =
|
mHdmiControlService =
|
||||||
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
||||||
Collections.emptyList()) {
|
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
AudioManager getAudioManager() {
|
AudioManager getAudioManager() {
|
||||||
return new AudioManager() {
|
return new AudioManager() {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.server.hdmi;
|
package com.android.server.hdmi;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
|
import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
|
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
|
||||||
@@ -92,7 +93,7 @@ public class HdmiCecLocalDevicePlaybackTest {
|
|||||||
|
|
||||||
mHdmiControlService =
|
mHdmiControlService =
|
||||||
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
||||||
Collections.emptyList()) {
|
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
void wakeUp() {
|
void wakeUp() {
|
||||||
mWokenUp = true;
|
mWokenUp = true;
|
||||||
@@ -151,6 +152,7 @@ public class HdmiCecLocalDevicePlaybackTest {
|
|||||||
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
||||||
mNativeWrapper.setPortConnectionStatus(1, true);
|
mNativeWrapper.setPortConnectionStatus(1, true);
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(context);
|
mPowerManager = new FakePowerManagerWrapper(context);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal);
|
mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package com.android.server.hdmi;
|
|||||||
|
|
||||||
import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
|
import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
|
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_TV;
|
import static com.android.server.hdmi.Constants.ADDR_TV;
|
||||||
@@ -137,7 +138,8 @@ public class HdmiCecLocalDeviceTest {
|
|||||||
Context context = InstrumentationRegistry.getTargetContext();
|
Context context = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
mHdmiControlService =
|
mHdmiControlService =
|
||||||
new HdmiControlService(context, Collections.emptyList()) {
|
new HdmiControlService(context, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
boolean isControlEnabled() {
|
boolean isControlEnabled() {
|
||||||
return isControlEnabled;
|
return isControlEnabled;
|
||||||
@@ -190,6 +192,7 @@ public class HdmiCecLocalDeviceTest {
|
|||||||
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
||||||
mNativeWrapper.setPortConnectionStatus(1, true);
|
mNativeWrapper.setPortConnectionStatus(1, true);
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
||||||
mNativeWrapper.setPhysicalAddress(0x2000);
|
mNativeWrapper.setPhysicalAddress(0x2000);
|
||||||
mTestLooper.dispatchAll();
|
mTestLooper.dispatchAll();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.server.hdmi;
|
package com.android.server.hdmi;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
|
import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
|
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
|
||||||
@@ -29,8 +30,10 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.eq;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.hdmi.HdmiControlManager;
|
import android.hardware.hdmi.HdmiControlManager;
|
||||||
@@ -125,7 +128,7 @@ public class HdmiCecLocalDeviceTvTest {
|
|||||||
|
|
||||||
mHdmiControlService =
|
mHdmiControlService =
|
||||||
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
||||||
Collections.emptyList()) {
|
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
void wakeUp() {
|
void wakeUp() {
|
||||||
mWokenUp = true;
|
mWokenUp = true;
|
||||||
@@ -179,6 +182,7 @@ public class HdmiCecLocalDeviceTvTest {
|
|||||||
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
|
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
|
||||||
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(context);
|
mPowerManager = new FakePowerManagerWrapper(context);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
||||||
@@ -768,7 +772,7 @@ public class HdmiCecLocalDeviceTvTest {
|
|||||||
// When the device reports its physical address, the listener eventually is invoked.
|
// When the device reports its physical address, the listener eventually is invoked.
|
||||||
HdmiCecMessage reportPhysicalAddress =
|
HdmiCecMessage reportPhysicalAddress =
|
||||||
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
|
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
|
||||||
ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
|
ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
|
||||||
mNativeWrapper.onCecMessage(reportPhysicalAddress);
|
mNativeWrapper.onCecMessage(reportPhysicalAddress);
|
||||||
mTestLooper.dispatchAll();
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
@@ -777,6 +781,54 @@ public class HdmiCecLocalDeviceTvTest {
|
|||||||
assertThat(mDeviceEventListeners.size()).isEqualTo(1);
|
assertThat(mDeviceEventListeners.size()).isEqualTo(1);
|
||||||
assertThat(mDeviceEventListeners.get(0).getStatus())
|
assertThat(mDeviceEventListeners.get(0).getStatus())
|
||||||
.isEqualTo(HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
|
.isEqualTo(HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void receiveSetAudioVolumeLevel_samNotActivated_noFeatureAbort_volumeChanges() {
|
||||||
|
when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
|
||||||
|
|
||||||
|
// Max volume of STREAM_MUSIC is retrieved on boot
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
|
||||||
|
ADDR_PLAYBACK_1,
|
||||||
|
ADDR_TV,
|
||||||
|
20));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
// <Feature Abort>[Not in correct mode] not sent
|
||||||
|
HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
|
||||||
|
ADDR_TV,
|
||||||
|
ADDR_PLAYBACK_1,
|
||||||
|
Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
|
||||||
|
Constants.ABORT_NOT_IN_CORRECT_MODE);
|
||||||
|
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortMessage);
|
||||||
|
|
||||||
|
// <Set Audio Volume Level> uses volume range [0, 100]; STREAM_MUSIC uses range [0, 25]
|
||||||
|
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5), anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void receiveSetAudioVolumeLevel_samActivated_respondsFeatureAbort_noVolumeChange() {
|
||||||
|
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
|
||||||
|
ADDR_AUDIO_SYSTEM, ADDR_TV, true));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
|
||||||
|
ADDR_PLAYBACK_1, ADDR_TV, 50));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
// <Feature Abort>[Not in correct mode] sent
|
||||||
|
HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
|
||||||
|
ADDR_TV,
|
||||||
|
ADDR_PLAYBACK_1,
|
||||||
|
Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
|
||||||
|
Constants.ABORT_NOT_IN_CORRECT_MODE);
|
||||||
|
assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage);
|
||||||
|
|
||||||
|
// AudioManager not notified of volume change
|
||||||
|
verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
|
||||||
|
anyInt());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ public class HdmiCecMessageValidatorTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
HdmiControlService mHdmiControlService = new HdmiControlService(
|
HdmiControlService mHdmiControlService = new HdmiControlService(
|
||||||
InstrumentationRegistry.getTargetContext(), Collections.emptyList());
|
InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper());
|
||||||
|
|
||||||
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
|
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ public class HdmiCecNetworkTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
mContext = InstrumentationRegistry.getTargetContext();
|
mContext = InstrumentationRegistry.getTargetContext();
|
||||||
mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList()) {
|
mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
|
void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
|
||||||
mDeviceEventListenerStatuses.add(status);
|
mDeviceEventListenerStatuses.add(status);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.server.hdmi;
|
package com.android.server.hdmi;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
@@ -65,7 +66,8 @@ public class HdmiCecPowerStatusControllerTest {
|
|||||||
Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||||
Looper myLooper = mTestLooper.getLooper();
|
Looper myLooper = mTestLooper.getLooper();
|
||||||
|
|
||||||
mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList()) {
|
mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
boolean isControlEnabled() {
|
boolean isControlEnabled() {
|
||||||
return true;
|
return true;
|
||||||
@@ -105,6 +107,7 @@ public class HdmiCecPowerStatusControllerTest {
|
|||||||
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
||||||
mNativeWrapper.setPortConnectionStatus(1, true);
|
mNativeWrapper.setPortConnectionStatus(1, true);
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(contextSpy);
|
mPowerManager = new FakePowerManagerWrapper(contextSpy);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mHdmiControlService.getHdmiCecNetwork().initPortInfo();
|
mHdmiControlService.getHdmiCecNetwork().initPortInfo();
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ public class HdmiControlServiceTest {
|
|||||||
|
|
||||||
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
|
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
|
||||||
|
|
||||||
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
|
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()));
|
||||||
doNothing().when(mHdmiControlServiceSpy)
|
doNothing().when(mHdmiControlServiceSpy)
|
||||||
.writeStringSystemProperty(anyString(), anyString());
|
.writeStringSystemProperty(anyString(), anyString());
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.server.hdmi;
|
package com.android.server.hdmi;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_TV;
|
import static com.android.server.hdmi.Constants.ADDR_TV;
|
||||||
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
||||||
import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
|
import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
|
||||||
@@ -87,7 +88,8 @@ public class OneTouchPlayActionTest {
|
|||||||
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||||
mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
|
mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
|
||||||
|
|
||||||
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
|
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
AudioManager getAudioManager() {
|
AudioManager getAudioManager() {
|
||||||
return new AudioManager() {
|
return new AudioManager() {
|
||||||
@@ -120,6 +122,7 @@ public class OneTouchPlayActionTest {
|
|||||||
mHdmiControlService.setCecController(hdmiCecController);
|
mHdmiControlService.setCecController(hdmiCecController);
|
||||||
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
|
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mPhysicalAddress = 0x2000;
|
mPhysicalAddress = 0x2000;
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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.hardware.hdmi.DeviceFeatures;
|
||||||
|
import android.hardware.hdmi.HdmiControlManager;
|
||||||
|
import android.hardware.hdmi.HdmiDeviceInfo;
|
||||||
|
import android.media.AudioDeviceAttributes;
|
||||||
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
import com.google.android.collect.Lists;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for Absolute Volume Control where the local device is a Playback device and the
|
||||||
|
* System Audio device is an Audio System.
|
||||||
|
*/
|
||||||
|
@SmallTest
|
||||||
|
@Presubmit
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class PlaybackDeviceToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
|
||||||
|
return new HdmiCecLocalDevicePlayback(hdmiControlService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPhysicalAddress() {
|
||||||
|
return 0x1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getDeviceType() {
|
||||||
|
return HdmiDeviceInfo.DEVICE_PLAYBACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AudioDeviceAttributes getAudioOutputDevice() {
|
||||||
|
return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSystemAudioDeviceLogicalAddress() {
|
||||||
|
return Constants.ADDR_AUDIO_SYSTEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSystemAudioDeviceType() {
|
||||||
|
return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AVC is disabled if the Audio System disables System Audio mode, and the TV has unknown
|
||||||
|
* support for <Set Audio Volume Level>. It is enabled once the TV confirms support for
|
||||||
|
* <Set Audio Volume Level> and sends <Report Audio Status>.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void switchToTv_absoluteVolumeControlDisabledUntilAllConditionsMet() {
|
||||||
|
enableAbsoluteVolumeControl();
|
||||||
|
|
||||||
|
// Audio System disables System Audio Mode. AVC should be disabled.
|
||||||
|
receiveSetSystemAudioMode(false);
|
||||||
|
verifyAbsoluteVolumeDisabled();
|
||||||
|
|
||||||
|
// TV reports support for <Set Audio Volume Level>
|
||||||
|
mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
|
||||||
|
Constants.ADDR_TV, HdmiControlManager.HDMI_CEC_VERSION_2_0,
|
||||||
|
Arrays.asList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
|
||||||
|
Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
|
||||||
|
DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
|
||||||
|
.setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED)
|
||||||
|
.build()));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
// TV reports its initial audio status
|
||||||
|
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
|
||||||
|
Constants.ADDR_TV,
|
||||||
|
getLogicalAddress(),
|
||||||
|
30,
|
||||||
|
false));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
verifyAbsoluteVolumeEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.mockito.Mockito.clearInvocations;
|
||||||
|
|
||||||
|
import android.hardware.hdmi.DeviceFeatures;
|
||||||
|
import android.hardware.hdmi.HdmiControlManager;
|
||||||
|
import android.hardware.hdmi.HdmiDeviceInfo;
|
||||||
|
import android.media.AudioDeviceAttributes;
|
||||||
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for Absolute Volume Control where the local device is a Playback device and the
|
||||||
|
* System Audio device is a TV.
|
||||||
|
*/
|
||||||
|
@SmallTest
|
||||||
|
@Presubmit
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class PlaybackDeviceToTvAvcTest extends BaseAbsoluteVolumeControlTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
|
||||||
|
return new HdmiCecLocalDevicePlayback(hdmiControlService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPhysicalAddress() {
|
||||||
|
return 0x1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getDeviceType() {
|
||||||
|
return HdmiDeviceInfo.DEVICE_PLAYBACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AudioDeviceAttributes getAudioOutputDevice() {
|
||||||
|
return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSystemAudioDeviceLogicalAddress() {
|
||||||
|
return Constants.ADDR_TV;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSystemAudioDeviceType() {
|
||||||
|
return HdmiDeviceInfo.DEVICE_TV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AVC is disabled when an Audio System with unknown support for <Set Audio Volume Level>
|
||||||
|
* becomes the System Audio device. It is enabled once the Audio System reports that it
|
||||||
|
* supports <Set Audio Volume Level> and sends <Report Audio Status>.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void switchToAudioSystem_absoluteVolumeControlDisabledUntilAllConditionsMet() {
|
||||||
|
enableAbsoluteVolumeControl();
|
||||||
|
|
||||||
|
// Audio System enables System Audio Mode. AVC should be disabled.
|
||||||
|
receiveSetSystemAudioMode(true);
|
||||||
|
verifyAbsoluteVolumeDisabled();
|
||||||
|
|
||||||
|
clearInvocations(mAudioManager, mAudioDeviceVolumeManager);
|
||||||
|
|
||||||
|
// Audio System reports support for <Set Audio Volume Level>
|
||||||
|
mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
|
||||||
|
Constants.ADDR_AUDIO_SYSTEM, HdmiControlManager.HDMI_CEC_VERSION_2_0,
|
||||||
|
Arrays.asList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_SOURCE,
|
||||||
|
Collections.emptyList(),
|
||||||
|
DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
|
||||||
|
.setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED)
|
||||||
|
.build()));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
// Audio system reports its initial audio status
|
||||||
|
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
|
||||||
|
Constants.ADDR_AUDIO_SYSTEM,
|
||||||
|
getLogicalAddress(),
|
||||||
|
30,
|
||||||
|
false));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
verifyAbsoluteVolumeEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.server.hdmi;
|
package com.android.server.hdmi;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
|
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
|
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
|
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
|
||||||
@@ -68,7 +69,8 @@ public class PowerStatusMonitorActionTest {
|
|||||||
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||||
|
|
||||||
mHdmiControlService = new HdmiControlService(mContextSpy,
|
mHdmiControlService = new HdmiControlService(mContextSpy,
|
||||||
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV)) {
|
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
AudioManager getAudioManager() {
|
AudioManager getAudioManager() {
|
||||||
return new AudioManager() {
|
return new AudioManager() {
|
||||||
@@ -110,6 +112,7 @@ public class PowerStatusMonitorActionTest {
|
|||||||
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
|
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
|
||||||
mNativeWrapper.setPortInfo(hdmiPortInfo);
|
mNativeWrapper.setPortInfo(hdmiPortInfo);
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mPhysicalAddress = 0x0000;
|
mPhysicalAddress = 0x0000;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.server.hdmi;
|
package com.android.server.hdmi;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
||||||
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
||||||
|
|
||||||
@@ -96,8 +97,8 @@ public class RequestSadActionTest {
|
|||||||
mMyLooper = mTestLooper.getLooper();
|
mMyLooper = mTestLooper.getLooper();
|
||||||
|
|
||||||
mHdmiControlService =
|
mHdmiControlService =
|
||||||
new HdmiControlService(context,
|
new HdmiControlService(context, Collections.emptyList(),
|
||||||
Collections.emptyList()) {
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
boolean isControlEnabled() {
|
boolean isControlEnabled() {
|
||||||
return true;
|
return true;
|
||||||
@@ -125,6 +126,7 @@ public class RequestSadActionTest {
|
|||||||
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
|
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
|
||||||
mLocalDevices.add(mHdmiCecLocalDeviceTv);
|
mLocalDevices.add(mHdmiCecLocalDeviceTv);
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(context);
|
mPowerManager = new FakePowerManagerWrapper(context);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.server.hdmi;
|
package com.android.server.hdmi;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
|
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
|
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
|
||||||
@@ -149,7 +150,7 @@ public class RoutingControlActionTest {
|
|||||||
|
|
||||||
mHdmiControlService =
|
mHdmiControlService =
|
||||||
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
|
||||||
Collections.emptyList()) {
|
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
boolean isControlEnabled() {
|
boolean isControlEnabled() {
|
||||||
return true;
|
return true;
|
||||||
@@ -186,6 +187,7 @@ public class RoutingControlActionTest {
|
|||||||
true, false, false);
|
true, false, false);
|
||||||
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(context);
|
mPowerManager = new FakePowerManagerWrapper(context);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
|
|||||||
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
|
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
|
||||||
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
|
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
@@ -81,7 +82,8 @@ public class SetAudioVolumeLevelDiscoveryActionTest {
|
|||||||
mContextSpy = spy(new ContextWrapper(
|
mContextSpy = spy(new ContextWrapper(
|
||||||
InstrumentationRegistry.getInstrumentation().getTargetContext()));
|
InstrumentationRegistry.getInstrumentation().getTargetContext()));
|
||||||
|
|
||||||
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
|
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()));
|
||||||
doNothing().when(mHdmiControlServiceSpy)
|
doNothing().when(mHdmiControlServiceSpy)
|
||||||
.writeStringSystemProperty(anyString(), anyString());
|
.writeStringSystemProperty(anyString(), anyString());
|
||||||
|
|
||||||
@@ -98,6 +100,7 @@ public class SetAudioVolumeLevelDiscoveryActionTest {
|
|||||||
mHdmiControlServiceSpy.setHdmiMhlController(
|
mHdmiControlServiceSpy.setHdmiMhlController(
|
||||||
HdmiMhlControllerStub.create(mHdmiControlServiceSpy));
|
HdmiMhlControllerStub.create(mHdmiControlServiceSpy));
|
||||||
mHdmiControlServiceSpy.initService();
|
mHdmiControlServiceSpy.initService();
|
||||||
|
mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
||||||
mHdmiControlServiceSpy.setPowerManager(mPowerManager);
|
mHdmiControlServiceSpy.setPowerManager(mPowerManager);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.server.hdmi;
|
package com.android.server.hdmi;
|
||||||
|
|
||||||
|
|
||||||
|
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||||
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
|
||||||
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
|
||||||
import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT;
|
import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT;
|
||||||
@@ -69,7 +70,8 @@ public class SystemAudioAutoInitiationActionTest {
|
|||||||
|
|
||||||
Looper myLooper = mTestLooper.getLooper();
|
Looper myLooper = mTestLooper.getLooper();
|
||||||
|
|
||||||
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
|
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
|
||||||
|
new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
AudioManager getAudioManager() {
|
AudioManager getAudioManager() {
|
||||||
return new AudioManager() {
|
return new AudioManager() {
|
||||||
@@ -108,6 +110,7 @@ public class SystemAudioAutoInitiationActionTest {
|
|||||||
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
|
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
|
||||||
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
mNativeWrapper.setPortInfo(hdmiPortInfos);
|
||||||
mHdmiControlService.initService();
|
mHdmiControlService.initService();
|
||||||
|
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
|
||||||
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
|
||||||
mHdmiControlService.setPowerManager(mPowerManager);
|
mHdmiControlService.setPowerManager(mPowerManager);
|
||||||
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class SystemAudioInitiationActionFromAvrTest {
|
|||||||
Context context = InstrumentationRegistry.getTargetContext();
|
Context context = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
HdmiControlService hdmiControlService = new HdmiControlService(context,
|
HdmiControlService hdmiControlService = new HdmiControlService(context,
|
||||||
Collections.emptyList()) {
|
Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
|
||||||
@Override
|
@Override
|
||||||
void sendCecCommand(
|
void sendCecCommand(
|
||||||
HdmiCecMessage command, @Nullable SendMessageCallback callback) {
|
HdmiCecMessage command, @Nullable SendMessageCallback callback) {
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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.hardware.hdmi.HdmiDeviceInfo;
|
||||||
|
import android.media.AudioDeviceAttributes;
|
||||||
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for Absolute Volume Control where the local device is a TV and the System Audio device
|
||||||
|
* is an Audio System. Assumes that the TV uses ARC (rather than eARC).
|
||||||
|
*/
|
||||||
|
@SmallTest
|
||||||
|
@Presubmit
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class TvToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
|
||||||
|
return new HdmiCecLocalDeviceTv(hdmiControlService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPhysicalAddress() {
|
||||||
|
return 0x0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getDeviceType() {
|
||||||
|
return HdmiDeviceInfo.DEVICE_TV;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AudioDeviceAttributes getAudioOutputDevice() {
|
||||||
|
return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSystemAudioDeviceLogicalAddress() {
|
||||||
|
return Constants.ADDR_AUDIO_SYSTEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSystemAudioDeviceType() {
|
||||||
|
return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user