diff --git a/services/core/Android.bp b/services/core/Android.bp index 9f369101dd7c3..0725bb277e510 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -127,7 +127,7 @@ java_library_static { "android.hardware.health-V2.0-java", "android.hardware.health-V2.1-java", "android.hardware.light-V1-java", - "android.hardware.tv.cec-V1.0-java", + "android.hardware.tv.cec-V1.1-java", "android.hardware.weaver-V1.0-java", "android.hardware.biometrics.face-V1-java", "android.hardware.biometrics.face-V1.0-java", diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 06bcada84ff98..1643ec162bc2a 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -50,6 +50,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.NoSuchElementException; import java.util.concurrent.ArrayBlockingQueue; import java.util.function.Predicate; @@ -149,6 +150,12 @@ final class HdmiCecController { * returns {@code null}. */ static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) { + HdmiCecController controller = createWithNativeWrapper(service, new NativeWrapperImpl11(), + atomWriter); + if (controller != null) { + return controller; + } + HdmiLogger.warning("Unable to use cec@1.1"); return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter); } @@ -312,7 +319,7 @@ final class HdmiCecController { } /** - * Return CEC version of the device. + * Return highest CEC version supported by this device. * *

Declared as package-private. accessed by {@link HdmiControlService} only. */ @@ -745,13 +752,202 @@ final class HdmiCecController { boolean nativeIsConnected(int port); } - private static final class NativeWrapperImpl implements NativeWrapper, + private static final class NativeWrapperImpl11 implements NativeWrapper, IHwBinder.DeathRecipient, getPhysicalAddressCallback { - private IHdmiCec mHdmiCec; + private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec; + @Nullable private HdmiCecCallback mCallback; + private final Object mLock = new Object(); private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; + + @Override + public String nativeInit() { + return (connectToHal() ? mHdmiCec.toString() : null); + } + + boolean connectToHal() { + try { + mHdmiCec = android.hardware.tv.cec.V1_1.IHdmiCec.getService(true); + try { + mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE); + } catch (RemoteException e) { + HdmiLogger.error("Couldn't link to death : ", e); + } + } catch (RemoteException | NoSuchElementException e) { + HdmiLogger.error("Couldn't connect to cec@1.1", e); + return false; + } + return true; + } + + @Override + public void onValues(int result, short addr) { + if (result == Result.SUCCESS) { + synchronized (mLock) { + mPhysicalAddress = new Short(addr).intValue(); + } + } + } + + @Override + public void serviceDied(long cookie) { + if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) { + HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting"); + connectToHal(); + // Reconnect the callback + if (mCallback != null) { + setCallback(mCallback); + } + } + } + + @Override + public void setCallback(HdmiCecCallback callback) { + mCallback = callback; + try { + mHdmiCec.setCallback_1_1(new HdmiCecCallback11(callback)); + } catch (RemoteException e) { + HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); + } + } + + @Override + public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { + android.hardware.tv.cec.V1_1.CecMessage message = + new android.hardware.tv.cec.V1_1.CecMessage(); + message.initiator = srcAddress; + message.destination = dstAddress; + message.body = new ArrayList<>(body.length); + for (byte b : body) { + message.body.add(b); + } + try { + return mHdmiCec.sendMessage_1_1(message); + } catch (RemoteException e) { + HdmiLogger.error("Failed to send CEC message : ", e); + return SendMessageResult.FAIL; + } + } + + @Override + public int nativeAddLogicalAddress(int logicalAddress) { + try { + return mHdmiCec.addLogicalAddress_1_1(logicalAddress); + } catch (RemoteException e) { + HdmiLogger.error("Failed to add a logical address : ", e); + return Result.FAILURE_INVALID_ARGS; + } + } + + @Override + public void nativeClearLogicalAddress() { + try { + mHdmiCec.clearLogicalAddress(); + } catch (RemoteException e) { + HdmiLogger.error("Failed to clear logical address : ", e); + } + } + + @Override + public int nativeGetPhysicalAddress() { + try { + mHdmiCec.getPhysicalAddress(this); + return mPhysicalAddress; + } catch (RemoteException e) { + HdmiLogger.error("Failed to get physical address : ", e); + return INVALID_PHYSICAL_ADDRESS; + } + } + + @Override + public int nativeGetVersion() { + try { + return mHdmiCec.getCecVersion(); + } catch (RemoteException e) { + HdmiLogger.error("Failed to get cec version : ", e); + return Result.FAILURE_UNKNOWN; + } + } + + @Override + public int nativeGetVendorId() { + try { + return mHdmiCec.getVendorId(); + } catch (RemoteException e) { + HdmiLogger.error("Failed to get vendor id : ", e); + return Result.FAILURE_UNKNOWN; + } + } + + @Override + public HdmiPortInfo[] nativeGetPortInfos() { + try { + ArrayList hdmiPortInfos = + mHdmiCec.getPortInfo(); + HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()]; + int i = 0; + for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) { + hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId, + portInfo.type, + portInfo.physicalAddress, + portInfo.cecSupported, + false, + portInfo.arcSupported); + i++; + } + return hdmiPortInfo; + } catch (RemoteException e) { + HdmiLogger.error("Failed to get port information : ", e); + return null; + } + } + + @Override + public void nativeSetOption(int flag, boolean enabled) { + try { + mHdmiCec.setOption(flag, enabled); + } catch (RemoteException e) { + HdmiLogger.error("Failed to set option : ", e); + } + } + + @Override + public void nativeSetLanguage(String language) { + try { + mHdmiCec.setLanguage(language); + } catch (RemoteException e) { + HdmiLogger.error("Failed to set language : ", e); + } + } + + @Override + public void nativeEnableAudioReturnChannel(int port, boolean flag) { + try { + mHdmiCec.enableAudioReturnChannel(port, flag); + } catch (RemoteException e) { + HdmiLogger.error("Failed to enable/disable ARC : ", e); + } + } + + @Override + public boolean nativeIsConnected(int port) { + try { + return mHdmiCec.isConnected(port); + } catch (RemoteException e) { + HdmiLogger.error("Failed to get connection info : ", e); + return false; + } + } + } + + private static final class NativeWrapperImpl implements NativeWrapper, + IHwBinder.DeathRecipient, getPhysicalAddressCallback { + private android.hardware.tv.cec.V1_0.IHdmiCec mHdmiCec; @Nullable private HdmiCecCallback mCallback; + private final Object mLock = new Object(); + private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; + @Override public String nativeInit() { return (connectToHal() ? mHdmiCec.toString() : null); @@ -765,8 +961,8 @@ final class HdmiCecController { } catch (RemoteException e) { HdmiLogger.error("Couldn't link to death : ", e); } - } catch (RemoteException e) { - HdmiLogger.error("Couldn't get tv.cec service : ", e); + } catch (RemoteException | NoSuchElementException e) { + HdmiLogger.error("Couldn't connect to cec@1.0", e); return false; } return true; @@ -776,7 +972,7 @@ final class HdmiCecController { public void setCallback(@NonNull HdmiCecCallback callback) { mCallback = callback; try { - mHdmiCec.setCallback(callback); + mHdmiCec.setCallback(new HdmiCecCallback10(callback)); } catch (RemoteException e) { HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); } @@ -931,20 +1127,69 @@ final class HdmiCecController { } } - final class HdmiCecCallback extends IHdmiCecCallback.Stub { + final class HdmiCecCallback { + public void onCecMessage(int initiator, int destination, byte[] body) { + runOnServiceThread( + () -> handleIncomingCecCommand(initiator, destination, body)); + } + + public void onHotplugEvent(int portId, boolean connected) { + runOnServiceThread(() -> handleHotplug(portId, connected)); + } + } + + private static final class HdmiCecCallback10 extends IHdmiCecCallback.Stub { + private final HdmiCecCallback mHdmiCecCallback; + + HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) { + mHdmiCecCallback = hdmiCecCallback; + } + @Override public void onCecMessage(CecMessage message) throws RemoteException { byte[] body = new byte[message.body.size()]; for (int i = 0; i < message.body.size(); i++) { body[i] = message.body.get(i); } - runOnServiceThread( - () -> handleIncomingCecCommand(message.initiator, message.destination, body)); + mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); } @Override public void onHotplugEvent(HotplugEvent event) throws RemoteException { - runOnServiceThread(() -> handleHotplug(event.portId, event.connected)); + mHdmiCecCallback.onHotplugEvent(event.portId, event.connected); + } + } + + private static final class HdmiCecCallback11 + extends android.hardware.tv.cec.V1_1.IHdmiCecCallback.Stub { + private final HdmiCecCallback mHdmiCecCallback; + + HdmiCecCallback11(HdmiCecCallback hdmiCecCallback) { + mHdmiCecCallback = hdmiCecCallback; + } + + @Override + public void onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message) + throws RemoteException { + byte[] body = new byte[message.body.size()]; + for (int i = 0; i < message.body.size(); i++) { + body[i] = message.body.get(i); + } + mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); + } + + @Override + public void onCecMessage(CecMessage message) throws RemoteException { + byte[] body = new byte[message.body.size()]; + for (int i = 0; i < message.body.size(); i++) { + body[i] = message.body.get(i); + } + mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); + } + + @Override + public void onHotplugEvent(HotplugEvent event) throws RemoteException { + mHdmiCecCallback.onHotplugEvent(event.portId, event.connected); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index bb57a69d6f51a..fcbd89781de4b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -15,12 +15,9 @@ */ package com.android.server.hdmi; +import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPortInfo; -import android.hardware.tv.cec.V1_0.CecMessage; -import android.hardware.tv.cec.V1_0.HotplugEvent; import android.hardware.tv.cec.V1_0.SendMessageResult; -import android.os.RemoteException; -import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.HdmiCecController.NativeWrapper; @@ -99,7 +96,7 @@ final class FakeNativeWrapper implements NativeWrapper { @Override public int nativeGetVersion() { - return 0; + return HdmiControlManager.HDMI_CEC_VERSION_2_0; } @Override @@ -139,20 +136,15 @@ final class FakeNativeWrapper implements NativeWrapper { if (mCallback == null) { return; } - CecMessage message = new CecMessage(); - message.initiator = hdmiCecMessage.getSource(); - message.destination = hdmiCecMessage.getDestination(); - ArrayList body = new ArrayList<>(); - body.add((byte) hdmiCecMessage.getOpcode()); + int source = hdmiCecMessage.getSource(); + int destination = hdmiCecMessage.getDestination(); + byte[] body = new byte[hdmiCecMessage.getParams().length + 1]; + int i = 0; + body[i++] = (byte) hdmiCecMessage.getOpcode(); for (byte param : hdmiCecMessage.getParams()) { - body.add(param); - } - message.body = body; - try { - mCallback.onCecMessage(message); - } catch (RemoteException e) { - Log.e(TAG, "Error sending CEC message", e); + body[i++] = param; } + mCallback.onCecMessage(source, destination, body); } public void onHotplugEvent(int port, boolean connected) { @@ -160,15 +152,7 @@ final class FakeNativeWrapper implements NativeWrapper { return; } - HotplugEvent hotplugEvent = new HotplugEvent(); - hotplugEvent.portId = port; - hotplugEvent.connected = connected; - - try { - mCallback.onHotplugEvent(hotplugEvent); - } catch (RemoteException e) { - Log.e(TAG, "Error sending hotplug event", e); - } + mCallback.onHotplugEvent(port, connected); } public List getResultMessages() {