Merge "Implement CEC HAL v1.1" into sc-dev
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9f36910..0725bb2 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -127,7 +127,7 @@
         "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 06bcada..1643ec1 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.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 @@
      *         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 @@
     }
 
     /**
-     * Return CEC version of the device.
+     * Return highest CEC version supported by this device.
      *
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
@@ -745,13 +752,202 @@
         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<android.hardware.tv.cec.V1_0.HdmiPortInfo> 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 @@
                 } 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 @@
         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 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 bb57a69..fcbd897 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 @@
 
     @Override
     public int nativeGetVersion() {
-        return 0;
+        return HdmiControlManager.HDMI_CEC_VERSION_2_0;
     }
 
     @Override
@@ -139,20 +136,15 @@
         if (mCallback == null) {
             return;
         }
-        CecMessage message = new CecMessage();
-        message.initiator = hdmiCecMessage.getSource();
-        message.destination = hdmiCecMessage.getDestination();
-        ArrayList<Byte> 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);
+            body[i++] = param;
         }
-        message.body = body;
-        try {
-            mCallback.onCecMessage(message);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error sending CEC message", e);
-        }
+        mCallback.onCecMessage(source, destination, body);
     }
 
     public void onHotplugEvent(int port, boolean connected) {
@@ -160,15 +152,7 @@
             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<HdmiCecMessage> getResultMessages() {