summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/hardware/hdmi/HdmiControlManager.java91
-rw-r--r--core/java/android/hardware/hdmi/IHdmiControlService.aidl3
-rw-r--r--core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl40
-rw-r--r--core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java10
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java64
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java140
6 files changed, 321 insertions, 27 deletions
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index d05ba799205c..d5dadbff419f 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -685,6 +685,28 @@ public final class HdmiControlManager {
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.
*/
public interface VendorCommandListener {
@@ -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);
+ }
+ };
+ }
+
}
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 95eaf75d6510..a8fed2b03cfc 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -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);
diff --git a/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl b/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl
new file mode 100644
index 000000000000..1407821b413c
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl
@@ -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);
+}
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 0a729a98a6a8..2141b815fb3b 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -194,6 +194,16 @@ public class HdmiAudioSystemClientTest {
}
@Override
+ public void addHdmiControlStatusChangeListener(
+ final IHdmiControlStatusChangeListener listener) {
+ }
+
+ @Override
+ public void removeHdmiControlStatusChangeListener(
+ final IHdmiControlStatusChangeListener listener) {
+ }
+
+ @Override
public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5bc2261878b6..50b6ced7f81e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -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,32 +5777,37 @@ 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) {
- 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");
- }
+ @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) return;
+ updateHdmiCecSinkLocked(isCecEnabled ? isCecAvailable : false);
+ }
+ }
+ };
+
private final Object mHdmiClientLock = new Object();
// If HDMI-CEC system audio is supported
@@ -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) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 3398d36ffdb9..a83dd215593f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -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);
}
}
@@ -1317,6 +1324,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 {
private final IHdmiHotplugEventListener mListener;
@@ -1621,6 +1659,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();
HdmiControlService.this.addHotplugEventListener(listener);
@@ -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;
}