blob: 7ba8e1433e1421aead7474f6c74d24585e1cc6e6 [file] [log] [blame]
/*
* Copyright (C) 2018 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 com.android.server.hdmi.Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioManager;
import android.os.SystemProperties;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
/**
* Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
* system.
*/
public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
// Whether System audio mode is activated or not.
// This becomes true only when all system audio sequences are finished.
@GuardedBy("mLock")
private boolean mSystemAudioActivated;
// Whether the System Audio Control feature is enabled or not. True by default.
@GuardedBy("mLock")
private boolean mSystemAudioControlFeatureEnabled;
protected Integer mSystemAudioSource;
private boolean mTvSystemAudioModeSupport;
protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
mSystemAudioControlFeatureEnabled = true;
// TODO(amyjojo) make System Audio Control controllable by users
/*mSystemAudioControlFeatureEnabled =
mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);*/
}
@Override
@ServiceThreadOnly
protected void onStandby(boolean initiatedByCec, int standbyAction) {
assertRunOnServiceThread();
mTvSystemAudioModeSupport = false;
// Record the last state of System Audio Control before going to standby
synchronized (mLock) {
SystemProperties.set(
Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL,
mSystemAudioActivated ? "true" : "false");
}
if (setSystemAudioMode(false)) {
mService.sendCecCommand(
HdmiCecMessageBuilder.buildSetSystemAudioMode(
mAddress, Constants.ADDR_BROADCAST, false));
}
}
@Override
@ServiceThreadOnly
protected void onAddressAllocated(int logicalAddress, int reason) {
assertRunOnServiceThread();
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
mAddress, mService.getPhysicalAddress(), mDeviceType));
mService.sendCecCommand(
HdmiCecMessageBuilder.buildDeviceVendorIdCommand(mAddress, mService.getVendorId()));
int systemAudioControlOnPowerOnProp =
SystemProperties.getInt(
PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON,
ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON);
boolean lastSystemAudioControlStatus =
SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
startQueuedActions();
}
@VisibleForTesting
protected void systemAudioControlOnPowerOn(
int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
|| ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
&& lastSystemAudioControlStatus)) {
addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
}
}
@Override
@ServiceThreadOnly
protected int getPreferredAddress() {
assertRunOnServiceThread();
return SystemProperties.getInt(
Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
}
@Override
@ServiceThreadOnly
protected void setPreferredAddress(int addr) {
assertRunOnServiceThread();
SystemProperties.set(
Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr));
}
@Override
@ServiceThreadOnly
protected boolean handleReportAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(amyjojo): implement report audio status handler
HdmiLogger.debug(TAG + "Stub handleReportAudioStatus");
return true;
}
@Override
@ServiceThreadOnly
protected boolean handleInitiateArc(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(amyjojo): implement initiate arc handler
HdmiLogger.debug(TAG + "Stub handleInitiateArc");
return true;
}
@Override
@ServiceThreadOnly
protected boolean handleReportArcInitiate(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(amyjojo): implement report arc initiate handler
HdmiLogger.debug(TAG + "Stub handleReportArcInitiate");
return true;
}
@Override
@ServiceThreadOnly
protected boolean handleReportArcTermination(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(amyjojo): implement report arc terminate handler
HdmiLogger.debug(TAG + "Stub handleReportArcTermination");
return true;
}
@Override
@ServiceThreadOnly
protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
reportAudioStatus(message);
return true;
}
@Override
@ServiceThreadOnly
protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportSystemAudioMode(
mAddress, message.getSource(), mSystemAudioActivated));
return true;
}
@Override
@ServiceThreadOnly
protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(b/80296911): Check if ARC supported.
// TODO(b/80296911): Check if port is ready to accept.
// TODO(b/80296911): if both true, activate ARC functinality and
mService.sendCecCommand(
HdmiCecMessageBuilder.buildInitiateArc(mAddress, message.getSource()));
// TODO(b/80296911): else, send <Feature Abort>["Unrecongnized opcode"]
return true;
}
@Override
@ServiceThreadOnly
protected boolean handleRequestArcTermination(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(b/80297105): Check if ARC supported.
// TODO(b/80297105): Check is currently in arc.
// TODO(b/80297105): If both true, deactivate ARC functionality and
mService.sendCecCommand(
HdmiCecMessageBuilder.buildTerminateArc(mAddress, message.getSource()));
// TODO(b/80297105): else, send <Feature Abort>["Unrecongnized opcode"]
return true;
}
@ServiceThreadOnly
protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(b/80297701): implement request short audio descriptor
HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor");
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
return true;
}
@Override
@ServiceThreadOnly
protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
assertRunOnServiceThread();
boolean systemAudioStatusOn = message.getParams().length != 0;
if (!setSystemAudioMode(systemAudioStatusOn)) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
return true;
}
mSystemAudioSource = systemAudioStatusOn ? message.getSource() : null;
mService.sendCecCommand(
HdmiCecMessageBuilder.buildSetSystemAudioMode(
mAddress, Constants.ADDR_BROADCAST, systemAudioStatusOn));
return true;
}
@Override
@ServiceThreadOnly
protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
assertRunOnServiceThread();
if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
}
return true;
}
@Override
@ServiceThreadOnly
protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
}
return true;
}
private void reportAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportAudioStatus(
mAddress, message.getSource(), scaledVolume, mute));
}
protected boolean setSystemAudioMode(boolean newSystemAudioMode) {
if (!isSystemAudioControlFeatureEnabled()) {
HdmiLogger.debug(
"Cannot turn "
+ (newSystemAudioMode ? "on" : "off")
+ "system audio mode "
+ "because the System Audio Control feature is disabled.");
return false;
}
HdmiLogger.debug(
"System Audio Mode change[old:%b new:%b]",
mSystemAudioActivated, newSystemAudioMode);
// Wake up device if System Audio Control is turned on but device is still on standby
if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) {
mService.wakeUp();
// TODO(amyjojo): Switch to the corresponding input
}
// Mute device when feature is turned off and unmute device when feature is turned on
boolean currentMuteStatus =
mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
if (currentMuteStatus == newSystemAudioMode) {
mService.getAudioManager()
.adjustStreamVolume(
AudioManager.STREAM_MUSIC,
newSystemAudioMode
? AudioManager.ADJUST_UNMUTE
: AudioManager.ADJUST_MUTE,
0);
}
updateAudioManagerForSystemAudio(newSystemAudioMode);
synchronized (mLock) {
if (mSystemAudioActivated != newSystemAudioMode) {
mSystemAudioActivated = newSystemAudioMode;
mService.announceSystemAudioModeChange(newSystemAudioMode);
}
}
return true;
}
private void updateAudioManagerForSystemAudio(boolean on) {
int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
}
protected boolean isSystemAudioControlFeatureEnabled() {
synchronized (mLock) {
return mSystemAudioControlFeatureEnabled;
}
}
protected boolean isSystemAudioActivated() {
synchronized (mLock) {
return mSystemAudioActivated;
}
}
/** Reports if System Audio Mode is supported by the connected TV */
interface TvSystemAudioModeSupportedCallback {
/** {@code supported} is true if the TV is connected and supports System Audio Mode. */
void onResult(boolean supported);
}
/**
* Queries the connected TV to detect if System Audio Mode is supported by the TV.
*
* <p>This query may take up to 2 seconds to complete.
*
* <p>The result of the query may be cached until Audio device type is put in standby or loses
* its physical address.
*/
void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) {
if (!mTvSystemAudioModeSupport) {
addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback));
} else {
callback.onResult(true);
}
}
void setTvSystemAudioModeSupport(boolean supported) {
mTvSystemAudioModeSupport = supported;
}
}