Merge "Honor HDMI_CONTROL_ENABLED setting in AudioService" am: 4efb0c510d
am: a40f432ac6
Change-Id: Ia4c92888f5ddc2e01a8b9025bdec0b00afb8c539
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user