[CEC Configuration] Add listener system API
Bug: 174731501
Bug: 172637440
Bug: 172905515
Test: atest com.google.android.gts.hdmicec.HdmiControlManagerHostTest
Change-Id: I830f3e312d5cf70fc84e389a12ecd50c2e1bfae6
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a6dfdac..9386d23 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2741,6 +2741,8 @@
}
public final class HdmiControlManager {
+ method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHdmiCecEnabledChangeListener(@NonNull android.hardware.hdmi.HdmiControlManager.CecSettingChangeListener);
+ method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHdmiCecEnabledChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.hdmi.HdmiControlManager.CecSettingChangeListener);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHotplugEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public java.util.List<java.lang.Integer> getAllowedCecSettingIntValues(@NonNull String);
@@ -2759,6 +2761,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public java.util.List<java.lang.String> getUserCecSettings();
method public boolean isDeviceConnected(@NonNull android.hardware.hdmi.HdmiDeviceInfo);
method public void powerOffDevice(@NonNull android.hardware.hdmi.HdmiDeviceInfo);
+ method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHdmiCecEnabledChangeListener(@NonNull android.hardware.hdmi.HdmiControlManager.CecSettingChangeListener);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
method public void setActiveSource(@NonNull android.hardware.hdmi.HdmiDeviceInfo);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setHdmiCecEnabled(@NonNull int);
@@ -2871,6 +2874,10 @@
field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa
}
+ public static interface HdmiControlManager.CecSettingChangeListener {
+ method public void onChange(@NonNull String);
+ }
+
@IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult {
}
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 38ffc80..b09eda4 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -1372,6 +1372,87 @@
}
/**
+ * Listener used to get setting change notification.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface CecSettingChangeListener {
+ /**
+ * Called when value of a setting changes.
+ *
+ * @param setting name of a CEC setting that changed
+ */
+ void onChange(@NonNull @CecSettingName String setting);
+ }
+
+ private final ArrayMap<String,
+ ArrayMap<CecSettingChangeListener, IHdmiCecSettingChangeListener>>
+ mCecSettingChangeListeners = new ArrayMap<>();
+
+ private void addCecSettingChangeListener(
+ @NonNull @CecSettingName String setting,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull CecSettingChangeListener listener) {
+ if (mService == null) {
+ Log.e(TAG, "HdmiControlService is not available");
+ return;
+ }
+ if (mCecSettingChangeListeners.containsKey(setting)
+ && mCecSettingChangeListeners.get(setting).containsKey(listener)) {
+ Log.e(TAG, "listener is already registered");
+ return;
+ }
+ IHdmiCecSettingChangeListener wrappedListener =
+ getCecSettingChangeListenerWrapper(executor, listener);
+ if (!mCecSettingChangeListeners.containsKey(setting)) {
+ mCecSettingChangeListeners.put(setting, new ArrayMap<>());
+ }
+ mCecSettingChangeListeners.get(setting).put(listener, wrappedListener);
+ try {
+ mService.addCecSettingChangeListener(setting, wrappedListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void removeCecSettingChangeListener(
+ @NonNull @CecSettingName String setting,
+ @NonNull CecSettingChangeListener listener) {
+ if (mService == null) {
+ Log.e(TAG, "HdmiControlService is not available");
+ return;
+ }
+ IHdmiCecSettingChangeListener wrappedListener =
+ !mCecSettingChangeListeners.containsKey(setting) ? null :
+ mCecSettingChangeListeners.get(setting).remove(listener);
+ if (wrappedListener == null) {
+ Log.e(TAG, "tried to remove not-registered listener");
+ return;
+ }
+ try {
+ mService.removeCecSettingChangeListener(setting, wrappedListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private IHdmiCecSettingChangeListener getCecSettingChangeListenerWrapper(
+ Executor executor, final CecSettingChangeListener listener) {
+ return new IHdmiCecSettingChangeListener.Stub() {
+ @Override
+ public void onChange(String setting) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onChange(setting));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+ }
+
+ /**
* Get a set of user-modifiable settings.
*
* @return a set of user-modifiable settings.
@@ -1493,6 +1574,53 @@
}
/**
+ * Add change listener for global status of HDMI CEC.
+ *
+ * <p>To stop getting the notification,
+ * use {@link #removeHdmiCecEnabledChangeListener(CecSettingChangeListener)}.
+ *
+ * Note that each invocation of the callback will be executed on an arbitrary
+ * Binder thread. This means that all callback implementations must be
+ * thread safe. To specify the execution thread, use
+ * {@link addHdmiCecEnabledChangeListener(Executor, CecSettingChangeListener)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public void addHdmiCecEnabledChangeListener(@NonNull CecSettingChangeListener listener) {
+ addHdmiCecEnabledChangeListener(ConcurrentUtils.DIRECT_EXECUTOR, listener);
+ }
+
+ /**
+ * Add change listener for global status of HDMI CEC.
+ *
+ * <p>To stop getting the notification,
+ * use {@link #removeHdmiCecEnabledChangeListener(CecSettingChangeListener)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public void addHdmiCecEnabledChangeListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull CecSettingChangeListener listener) {
+ addCecSettingChangeListener(CEC_SETTING_NAME_HDMI_CEC_ENABLED, executor, listener);
+ }
+
+ /**
+ * Remove change listener for global status of HDMI CEC.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public void removeHdmiCecEnabledChangeListener(
+ @NonNull CecSettingChangeListener listener) {
+ removeCecSettingChangeListener(CEC_SETTING_NAME_HDMI_CEC_ENABLED, listener);
+ }
+
+ /**
* Set the version of the HDMI CEC specification currently used.
*
* <p>Allows to select either CEC 1.4b or 2.0 to be used by the device.
diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
index 3b61911f..89a7afa8 100644
--- a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
+++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
@@ -306,6 +306,18 @@
}
@Override
+ public void addCecSettingChangeListener(String name,
+ IHdmiCecSettingChangeListener listener) {
+ HdmiControlServiceWrapper.this.addCecSettingChangeListener(name, listener);
+ }
+
+ @Override
+ public void removeCecSettingChangeListener(String name,
+ IHdmiCecSettingChangeListener listener) {
+ HdmiControlServiceWrapper.this.removeCecSettingChangeListener(name, listener);
+ }
+
+ @Override
public List<String> getUserCecSettings() {
return HdmiControlServiceWrapper.this.getUserCecSettings();
}
@@ -522,6 +534,14 @@
IHdmiCecVolumeControlFeatureListener listener) {}
/** @hide */
+ public void addCecSettingChangeListener(String name,
+ IHdmiCecSettingChangeListener listener) {}
+
+ /** @hide */
+ public void removeCecSettingChangeListener(String name,
+ IHdmiCecSettingChangeListener listener) {}
+
+ /** @hide */
public List<String> getUserCecSettings() {
return new ArrayList<>();
}
diff --git a/core/java/android/hardware/hdmi/IHdmiCecSettingChangeListener.aidl b/core/java/android/hardware/hdmi/IHdmiCecSettingChangeListener.aidl
new file mode 100644
index 0000000..6f7a6f8
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiCecSettingChangeListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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;
+
+/**
+ * Callback interface definition for HDMI client to get informed of
+ * CEC setting change.
+ *
+ * @hide
+ */
+oneway interface IHdmiCecSettingChangeListener {
+ void onChange(in String setting);
+}
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 65bd856..d7329e0 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -18,6 +18,7 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiCecSettingChangeListener;
import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
@@ -89,6 +90,8 @@
boolean isHdmiCecVolumeControlEnabled();
void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute);
void setSystemAudioModeOnForAudioOnlySource();
+ void addCecSettingChangeListener(String name, IHdmiCecSettingChangeListener listener);
+ void removeCecSettingChangeListener(String name, IHdmiCecSettingChangeListener listener);
List<String> getUserCecSettings();
List<String> getAllowedCecSettingStringValues(String name);
int[] getAllowedCecSettingIntValues(String name);
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 9531181d..6019b90 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -395,6 +395,16 @@
}
@Override
+ public void addCecSettingChangeListener(String name,
+ IHdmiCecSettingChangeListener listener) {
+ }
+
+ @Override
+ public void removeCecSettingChangeListener(String name,
+ IHdmiCecSettingChangeListener listener) {
+ }
+
+ @Override
public int[] getAllowedCecSettingIntValues(String name) {
return new int[0];
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 195d506..33f1fb4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -40,6 +40,7 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiHotplugEvent;
import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiCecSettingChangeListener;
import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.IHdmiControlService;
@@ -74,6 +75,7 @@
import android.provider.Settings.Global;
import android.sysprop.HdmiProperties;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
@@ -308,6 +310,11 @@
private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
new ArrayList<>();
+ // List of records for CEC setting change listener to handle the caller killed in action.
+ @GuardedBy("mLock")
+ private final ArrayMap<String, RemoteCallbackList<IHdmiCecSettingChangeListener>>
+ mHdmiCecSettingChangeListenerRecords = new ArrayMap<>();
+
@GuardedBy("mLock")
private InputChangeListenerRecord mInputChangeListenerRecord;
@@ -2222,6 +2229,20 @@
}
@Override
+ public void addCecSettingChangeListener(String name,
+ final IHdmiCecSettingChangeListener listener) {
+ enforceAccessPermission();
+ HdmiControlService.this.addCecSettingChangeListener(name, listener);
+ }
+
+ @Override
+ public void removeCecSettingChangeListener(String name,
+ final IHdmiCecSettingChangeListener listener) {
+ enforceAccessPermission();
+ HdmiControlService.this.removeCecSettingChangeListener(name, listener);
+ }
+
+ @Override
public List<String> getUserCecSettings() {
enforceAccessPermission();
long token = Binder.clearCallingIdentity();
@@ -3450,4 +3471,53 @@
protected HdmiCecConfig getHdmiCecConfig() {
return mHdmiCecConfig;
}
+
+ private HdmiCecConfig.SettingChangeListener mSettingChangeListener =
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String name) {
+ synchronized (mLock) {
+ if (!mHdmiCecSettingChangeListenerRecords.containsKey(name)) {
+ return;
+ }
+ mHdmiCecSettingChangeListenerRecords.get(name).broadcast(listener -> {
+ invokeCecSettingChangeListenerLocked(name, listener);
+ });
+ }
+ }
+ };
+
+ private void addCecSettingChangeListener(String name,
+ final IHdmiCecSettingChangeListener listener) {
+ synchronized (mLock) {
+ if (!mHdmiCecSettingChangeListenerRecords.containsKey(name)) {
+ mHdmiCecSettingChangeListenerRecords.put(name, new RemoteCallbackList<>());
+ mHdmiCecConfig.registerChangeListener(name, mSettingChangeListener);
+ }
+ mHdmiCecSettingChangeListenerRecords.get(name).register(listener);
+ }
+ }
+
+ private void removeCecSettingChangeListener(String name,
+ final IHdmiCecSettingChangeListener listener) {
+ synchronized (mLock) {
+ if (!mHdmiCecSettingChangeListenerRecords.containsKey(name)) {
+ return;
+ }
+ mHdmiCecSettingChangeListenerRecords.get(name).unregister(listener);
+ if (mHdmiCecSettingChangeListenerRecords.get(name).getRegisteredCallbackCount() == 0) {
+ mHdmiCecSettingChangeListenerRecords.remove(name);
+ mHdmiCecConfig.removeChangeListener(name, mSettingChangeListener);
+ }
+ }
+ }
+
+ private void invokeCecSettingChangeListenerLocked(String name,
+ final IHdmiCecSettingChangeListener listener) {
+ try {
+ listener.onChange(name);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report setting change", e);
+ }
+ }
}