Merge "Honor HDMI_CONTROL_ENABLED setting in AudioService" am: 4efb0c510d

am: a40f432ac6

Change-Id: Ia4c92888f5ddc2e01a8b9025bdec0b00afb8c539
This commit is contained in:
Madhava Srinivasan
2019-09-13 10:38:05 -07:00
committed by android-build-merger
6 changed files with 321 additions and 27 deletions

View File

@@ -684,6 +684,28 @@ public final class HdmiControlManager {
private final ArrayMap<HotplugEventListener, IHdmiHotplugEventListener>
mHotplugEventListeners = new ArrayMap<>();
/**
* Listener used to get HDMI Control (CEC) status (enabled/disabled) and the connected display
* status.
* @hide
*/
public interface HdmiControlStatusChangeListener {
/**
* Called when HDMI Control (CEC) is enabled/disabled.
*
* @param isCecEnabled status of HDMI Control
* {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled.
* @param isCecAvailable status of CEC support of the connected display (the TV).
* {@code true} if supported.
*
* Note: Value of isCecAvailable is only valid when isCecEnabled is true.
**/
void onStatusChange(boolean isCecEnabled, boolean isCecAvailable);
}
private final ArrayMap<HdmiControlStatusChangeListener, IHdmiControlStatusChangeListener>
mHdmiControlStatusChangeListeners = new ArrayMap<>();
/**
* Listener used to get vendor-specific commands.
*/
@@ -777,4 +799,73 @@ public final class HdmiControlManager {
}
};
}
/**
* Adds a listener to get informed of {@link HdmiControlStatusChange}.
*
* <p>To stop getting the notification,
* use {@link #removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener)}.
*
* @param listener {@link HdmiControlStatusChangeListener} instance
* @see HdmiControlManager#removeHdmiControlStatusChangeListener(
* HdmiControlStatusChangeListener)
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void addHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
if (mService == null) {
Log.e(TAG, "HdmiControlService is not available");
return;
}
if (mHdmiControlStatusChangeListeners.containsKey(listener)) {
Log.e(TAG, "listener is already registered");
return;
}
IHdmiControlStatusChangeListener wrappedListener =
getHdmiControlStatusChangeListenerWrapper(listener);
mHdmiControlStatusChangeListeners.put(listener, wrappedListener);
try {
mService.addHdmiControlStatusChangeListener(wrappedListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Removes a listener to stop getting informed of {@link HdmiControlStatusChange}.
*
* @param listener {@link HdmiControlStatusChangeListener} instance to be removed
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.HDMI_CEC)
public void removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
if (mService == null) {
Log.e(TAG, "HdmiControlService is not available");
return;
}
IHdmiControlStatusChangeListener wrappedListener =
mHdmiControlStatusChangeListeners.remove(listener);
if (wrappedListener == null) {
Log.e(TAG, "tried to remove not-registered listener");
return;
}
try {
mService.removeHdmiControlStatusChangeListener(wrappedListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private IHdmiControlStatusChangeListener getHdmiControlStatusChangeListenerWrapper(
final HdmiControlStatusChangeListener listener) {
return new IHdmiControlStatusChangeListener.Stub() {
@Override
public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) {
listener.onStatusChange(isCecEnabled, isCecAvailable);
}
};
}
}

View File

@@ -19,6 +19,7 @@ package android.hardware.hdmi;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
import android.hardware.hdmi.IHdmiDeviceEventListener;
import android.hardware.hdmi.IHdmiHotplugEventListener;
import android.hardware.hdmi.IHdmiInputChangeListener;
@@ -41,6 +42,8 @@ interface IHdmiControlService {
HdmiDeviceInfo getActiveSource();
void oneTouchPlay(IHdmiControlCallback callback);
void queryDisplayStatus(IHdmiControlCallback callback);
void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
void addHotplugEventListener(IHdmiHotplugEventListener listener);
void removeHotplugEventListener(IHdmiHotplugEventListener listener);
void addDeviceEventListener(IHdmiDeviceEventListener listener);

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2019 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 android.hardware.hdmi;
import android.hardware.hdmi.HdmiDeviceInfo;
/**
* Callback interface definition for HDMI client to get informed of
* the CEC availability change event.
*
* @hide
*/
oneway interface IHdmiControlStatusChangeListener {
/**
* Called when HDMI Control (CEC) is enabled/disabled.
*
* @param isCecEnabled status of HDMI Control
* {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled.
* @param isCecAvailable status of CEC support of the connected display (the TV).
* {@code true} if supported.
*
* Note: Value of isCecAvailable is only valid when isCecEnabled is true.
**/
void onStatusChange(boolean isCecEnabled, boolean isCecAvailable);
}

View File

@@ -193,6 +193,16 @@ public class HdmiAudioSystemClientTest {
public void queryDisplayStatus(final IHdmiControlCallback callback) {
}
@Override
public void addHdmiControlStatusChangeListener(
final IHdmiControlStatusChangeListener listener) {
}
@Override
public void removeHdmiControlStatusChangeListener(
final IHdmiControlStatusChangeListener listener) {
}
@Override
public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
}

View File

@@ -845,7 +845,12 @@ public class AudioService extends IAudioService.Stub
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
synchronized (mHdmiClientLock) {
mHdmiCecSink = false;
mHdmiManager = mContext.getSystemService(HdmiControlManager.class);
if (mHdmiManager != null) {
mHdmiManager.addHdmiControlStatusChangeListener(
mHdmiControlStatusChangeListenerCallback);
}
mHdmiTvClient = mHdmiManager.getTvClient();
if (mHdmiTvClient != null) {
mFixedVolumeDevices &= ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER;
@@ -856,7 +861,6 @@ public class AudioService extends IAudioService.Stub
mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
}
mHdmiCecSink = false;
mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient();
}
}
@@ -1113,8 +1117,7 @@ public class AudioService extends IAudioService.Stub
checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, caller);
synchronized (mHdmiClientLock) {
if (mHdmiManager != null && mHdmiPlaybackClient != null) {
mHdmiCecSink = false;
mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
updateHdmiCecSinkLocked(mHdmiCecSink | false);
}
}
}
@@ -1124,7 +1127,7 @@ public class AudioService extends IAudioService.Stub
if (isPlatformTelevision()) {
synchronized (mHdmiClientLock) {
if (mHdmiManager != null) {
mHdmiCecSink = false;
updateHdmiCecSinkLocked(mHdmiCecSink | false);
}
}
}
@@ -5774,31 +5777,36 @@ public class AudioService extends IAudioService.Stub
// are transformed into key events for the HDMI playback client.
//==========================================================================================
private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback {
public void onComplete(int status) {
@GuardedBy("mHdmiClientLock")
private void updateHdmiCecSinkLocked(boolean hdmiCecSink) {
mHdmiCecSink = hdmiCecSink;
if (mHdmiCecSink) {
if (DEBUG_VOL) {
Log.d(TAG, "CEC sink: setting HDMI as full vol device");
}
mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
} else {
if (DEBUG_VOL) {
Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device");
}
// Android TV devices without CEC service apply software volume on
// HDMI output
mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
}
checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI,
"HdmiPlaybackClient.DisplayStatusCallback");
}
private class MyHdmiControlStatusChangeListenerCallback
implements HdmiControlManager.HdmiControlStatusChangeListener {
public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) {
synchronized (mHdmiClientLock) {
if (mHdmiManager != null) {
mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN);
// Television devices without CEC service apply software volume on HDMI output
if (mHdmiCecSink) {
if (DEBUG_VOL) {
Log.d(TAG, "CEC sink: setting HDMI as full vol device");
}
mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
} else {
if (DEBUG_VOL) {
Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device");
}
// Android TV devices without CEC service apply software volume on
// HDMI output
mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
}
checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI,
"HdmiPlaybackClient.DisplayStatusCallback");
}
if (mHdmiManager == null) return;
updateHdmiCecSinkLocked(isCecEnabled ? isCecAvailable : false);
}
}
}
};
private final Object mHdmiClientLock = new Object();
@@ -5815,12 +5823,14 @@ public class AudioService extends IAudioService.Stub
@GuardedBy("mHdmiClientLock")
private HdmiPlaybackClient mHdmiPlaybackClient;
// true if we are a set-top box, an HDMI sink is connected and it supports CEC.
@GuardedBy("mHdmiClientLock")
private boolean mHdmiCecSink;
// Set only when device is an audio system.
@GuardedBy("mHdmiClientLock")
private HdmiAudioSystemClient mHdmiAudioSystemClient;
private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback();
private MyHdmiControlStatusChangeListenerCallback mHdmiControlStatusChangeListenerCallback =
new MyHdmiControlStatusChangeListenerCallback();
@Override
public int setHdmiSystemAudioSupported(boolean on) {

View File

@@ -42,6 +42,7 @@ import android.hardware.hdmi.HdmiHotplugEvent;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.IHdmiControlService;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
import android.hardware.hdmi.IHdmiDeviceEventListener;
import android.hardware.hdmi.IHdmiHotplugEventListener;
import android.hardware.hdmi.IHdmiInputChangeListener;
@@ -251,6 +252,11 @@ public class HdmiControlService extends SystemService {
// Type of logical devices hosted in the system. Stored in the unmodifiable list.
private final List<Integer> mLocalDevices;
// List of records for HDMI control status change listener for death monitoring.
@GuardedBy("mLock")
private final ArrayList<HdmiControlStatusChangeListenerRecord>
mHdmiControlStatusChangeListenerRecords = new ArrayList<>();
// List of records for hotplug event listener to handle the the caller killed in action.
@GuardedBy("mLock")
private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
@@ -564,6 +570,7 @@ public class HdmiControlService extends SystemService {
}
if (reason != -1) {
invokeVendorCommandListenersOnControlStateChanged(true, reason);
announceHdmiControlStatusChange(true);
}
}
@@ -1315,6 +1322,37 @@ public class HdmiControlService extends SystemService {
}
}
// Record class that monitors the event of the caller of being killed. Used to clean up
// the listener list and record list accordingly.
private final class HdmiControlStatusChangeListenerRecord implements IBinder.DeathRecipient {
private final IHdmiControlStatusChangeListener mListener;
HdmiControlStatusChangeListenerRecord(IHdmiControlStatusChangeListener listener) {
mListener = listener;
}
@Override
public void binderDied() {
synchronized (mLock) {
mHdmiControlStatusChangeListenerRecords.remove(this);
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof HdmiControlStatusChangeListenerRecord)) return false;
if (obj == this) return true;
HdmiControlStatusChangeListenerRecord other =
(HdmiControlStatusChangeListenerRecord) obj;
return other.mListener == this.mListener;
}
@Override
public int hashCode() {
return mListener.hashCode();
}
}
// Record class that monitors the event of the caller of being killed. Used to clean up
// the listener list and record list accordingly.
private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
@@ -1620,6 +1658,20 @@ public class HdmiControlService extends SystemService {
});
}
@Override
public void addHdmiControlStatusChangeListener(
final IHdmiControlStatusChangeListener listener) {
enforceAccessPermission();
HdmiControlService.this.addHdmiControlStatusChangeListener(listener);
}
@Override
public void removeHdmiControlStatusChangeListener(
final IHdmiControlStatusChangeListener listener) {
enforceAccessPermission();
HdmiControlService.this.removeHdmiControlStatusChangeListener(listener);
}
@Override
public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
enforceAccessPermission();
@@ -2135,6 +2187,51 @@ public class HdmiControlService extends SystemService {
source.queryDisplayStatus(callback);
}
private void addHdmiControlStatusChangeListener(
final IHdmiControlStatusChangeListener listener) {
final HdmiControlStatusChangeListenerRecord record =
new HdmiControlStatusChangeListenerRecord(listener);
try {
listener.asBinder().linkToDeath(record, 0);
} catch (RemoteException e) {
Slog.w(TAG, "Listener already died");
return;
}
synchronized (mLock) {
mHdmiControlStatusChangeListenerRecords.add(record);
}
// Inform the listener of the initial state of each HDMI port by generating
// hotplug events.
runOnServiceThread(new Runnable() {
@Override
public void run() {
synchronized (mLock) {
if (!mHdmiControlStatusChangeListenerRecords.contains(record)) return;
}
// Return the current status of mHdmiControlEnabled;
synchronized (mLock) {
invokeHdmiControlStatusChangeListenerLocked(listener, mHdmiControlEnabled);
}
}
});
}
private void removeHdmiControlStatusChangeListener(
final IHdmiControlStatusChangeListener listener) {
synchronized (mLock) {
for (HdmiControlStatusChangeListenerRecord record :
mHdmiControlStatusChangeListenerRecords) {
if (record.mListener.asBinder() == listener.asBinder()) {
listener.asBinder().unlinkToDeath(record, 0);
mHdmiControlStatusChangeListenerRecords.remove(record);
break;
}
}
}
}
private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
try {
@@ -2367,6 +2464,47 @@ public class HdmiControlService extends SystemService {
}
}
private void announceHdmiControlStatusChange(boolean isEnabled) {
assertRunOnServiceThread();
synchronized (mLock) {
for (HdmiControlStatusChangeListenerRecord record :
mHdmiControlStatusChangeListenerRecords) {
invokeHdmiControlStatusChangeListenerLocked(record.mListener, isEnabled);
}
}
}
private void invokeHdmiControlStatusChangeListenerLocked(
IHdmiControlStatusChangeListener listener, boolean isEnabled) {
if (isEnabled) {
queryDisplayStatus(new IHdmiControlCallback.Stub() {
public void onComplete(int status) {
boolean isAvailable = true;
if (status == HdmiControlManager.POWER_STATUS_UNKNOWN
|| status == HdmiControlManager.RESULT_EXCEPTION
|| status == HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE) {
isAvailable = false;
}
try {
listener.onStatusChange(isEnabled, isAvailable);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
+ " isAvailable: " + isAvailable, e);
}
}
});
return;
}
try {
listener.onStatusChange(isEnabled, false);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
+ " isAvailable: " + false, e);
}
}
public HdmiCecLocalDeviceTv tv() {
return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
}
@@ -2736,6 +2874,8 @@ public class HdmiControlService extends SystemService {
disableHdmiControlService();
}
});
announceHdmiControlStatusChange(enabled);
return;
}