title: HDMI CEC on android
body: <p>I've been facing a problem to access to HDMI CEC on <a href="https://pt.gearbest.com/tv-stick-dongle/pp_666670.html?wid=38&amp;currency=EUR&amp;vip=2051418&amp;gclid=CjwKCAiAjuPRBRBxEiwAeQ2QPhmGaCXK4xOhLSOnePIvLUeg4N-kr4VNYlHGi9EULmpGI4fiGl7EfxoCi7UQAvD_BwE" rel="noreferrer">this</a> android dongle. </p>
  
  <p>I'm trying to turn on the tv and change the input source of the tv but I was unable to it.</p>
  
  <p><strong>Android API Approach</strong></p>
  
  <p>I'm running a system app and I have settled </p>
  
  <pre><code>&lt;uses-permission android:name="android.permission.HDMI_CEC" /&gt; 
  </code></pre>
  
  <p>on AndroidManifest.xml.</p>
  
  <p>I'm accessing to HDMI service through reflection since I was not able to access it directly, even being a system app. </p>
  
  <pre><code>public class HdmiHelper {
  
      public HdmiHelper(Context context) {
          init(context);
      }
  
      public void init(Context context) {
  
          try {
  
              //Interface Callback Proxy
              Class&lt;?&gt; hotplugEventListenerClass = Class.forName("android.hardware.hdmi.HdmiControlManager$HotplugEventListener");
              Class&lt;?&gt; vendorCommandListenerClass = Class.forName("android.hardware.hdmi.HdmiControlManager$VendorCommandListener");
              Class&lt;?&gt; oneTouchPlayCallbackClass = Class.forName("android.hardware.hdmi.HdmiPlaybackClient$OneTouchPlayCallback");
              Class&lt;?&gt; displayStatusCallbackClass = Class.forName("android.hardware.hdmi.HdmiPlaybackClient$DisplayStatusCallback");
  
              Object interfaceOneTouchPlaybackCallback = Proxy.newProxyInstance(oneTouchPlayCallbackClass.getClassLoader(),
                      new Class&lt;?&gt;[]{ oneTouchPlayCallbackClass } , new callbackProxyListener() );
  
              Object interfaceHotplugEventCallback = Proxy.newProxyInstance(hotplugEventListenerClass.getClassLoader(),
                      new Class&lt;?&gt;[]{ hotplugEventListenerClass } , new callbackProxyListener() );
  
              Object interfaceDisplayStatusCallbackClass = Proxy.newProxyInstance(displayStatusCallbackClass.getClassLoader(),
                      new Class&lt;?&gt;[]{ displayStatusCallbackClass } , new callbackProxyListener() );
  
  
              Method m = context.getClass().getMethod("getSystemService", String.class);
              Object obj_HdmiControlManager = m.invoke(context, (Object) "hdmi_control");
  
              Log.d("HdmiHelper", "obj: " + obj_HdmiControlManager + " | " + obj_HdmiControlManager.getClass());
  
              for( Method method : obj_HdmiControlManager.getClass().getMethods()) {
                  Log.d("HdmiHelper", "   method: " + method.getName() );
              }
  
  
              Method method_addHotplugEventListener = obj_HdmiControlManager.getClass().getMethod("addHotplugEventListener", hotplugEventListenerClass);
              method_addHotplugEventListener.invoke(obj_HdmiControlManager, interfaceHotplugEventCallback);
  
  
              Method m2 = obj_HdmiControlManager.getClass().getMethod("getPlaybackClient");
              Object obj_HdmiPlaybackClient = m2.invoke( obj_HdmiControlManager );
              Log.d("HdmiHelper", "obj_HdmiPlaybackClient: " + obj_HdmiPlaybackClient + " | " + obj_HdmiPlaybackClient.getClass());
  
              Method method_oneTouchPlay = obj_HdmiPlaybackClient.getClass().getMethod("oneTouchPlay", oneTouchPlayCallbackClass);
              method_oneTouchPlay.invoke( obj_HdmiPlaybackClient, interfaceOneTouchPlaybackCallback);
  
  
              Method method_queryDisplayStatus = obj_HdmiPlaybackClient.getClass().getMethod("queryDisplayStatus", displayStatusCallbackClass);
  
              method_queryDisplayStatus.invoke( obj_HdmiPlaybackClient, interfaceDisplayStatusCallbackClass);
  
              Method method_getActiveSource = obj_HdmiPlaybackClient.getClass().getMethod("getActiveSource");
              Log.d("HdmiHelper", "getActiveSource: " + method_getActiveSource.invoke(obj_HdmiPlaybackClient));
  
  
          }catch (Exception e) {
              e.printStackTrace();
          }
  
      }
  
      public class callbackProxyListener implements java.lang.reflect.InvocationHandler {
  
          public callbackProxyListener() {
  
          }
  
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  
              try {
                  Log.d("HdmiHelper", "Start method " + method.getName() + " | " + proxy.getClass() + " | " + method.getDeclaringClass() );
  
                  if ( args != null ) {
  
                      // Prints the method being invoked
                      for (int i = 0; i != args.length; i++) {
                          Log.d("HdmiHelper", "  - Arg(" + i + "): " + args[i].toString());
                      }
  
                  }
  
                  if (method.getName().equals("onReceived")) {
  
                      if (args.length == 1) {
                          onReceived(args[0]);
                      }else
                      if (args.length == 3) {
                          onReceived( (int) args[0], BytesUtil.toByteArray( args[1] ), (boolean) args[2]  );
                      }
  
  
                  }else
                  if (method.getName().equals("onComplete")) {
                      onComplete( (int) args[0] );
                  }else
                  if (method.getName().equals("toString")) {
                      return this.toString();
                  }else {
                      return method.invoke(this, args);
                  }
  
              }catch (Exception e) {
                  e.printStackTrace();
              }
  
              return null;
          }
  
          void onComplete(int result) {
              Log.d("HdmiHelper", "onComplete: " + result);
          }
  
          void onReceived(Object event) {
  
              Class eventClass = event.getClass();
  
              Log.d("HdmiHelper", "onReceived(1): " + event.toString() + " | " + eventClass);
  
              try {
                  Method method_getPort = eventClass.getMethod("getPort");
                  Method method_isConnected = eventClass.getMethod("isConnected");
                  Method method_describeContents = eventClass.getMethod("describeContents");
  
                  Log.d("HdmiHelper", "    - " + method_getPort.invoke(event) + " | " + method_isConnected.invoke(event) + " | " + method_describeContents.invoke(event) );
  
              }catch (Exception e) {
  
                  e.printStackTrace();
  
              }
  
  
          }
  
  
          void onReceived(int srcAddress, byte[] params, boolean hasVendorId) {
              Log.d("HdmiHelper", "onReceived(3): " + srcAddress + " | " + params + " | " + hasVendorId);
          }
  
  
      }
  </code></pre>
  
  <p>Log answers:</p>
  
  <pre><code>D/HdmiHelper: obj: android.hardware.hdmi.HdmiControlManager@7bca63c | class android.hardware.hdmi.HdmiControlManager
  
  D/HdmiHelper: obj_HdmiPlaybackClient: android.hardware.hdmi.HdmiPlaybackClient@6345d1a | class android.hardware.hdmi.HdmiPlaybackClient
  
  D/HdmiHelper: Start method onReceived | class $Proxy2 | interface android.hardware.hdmi.HdmiControlManager$HotplugEventListener
  D/HdmiHelper: onReceived(1): android.hardware.hdmi.HdmiHotplugEvent@4c5c04b | class android.hardware.hdmi.HdmiHotplugEvent
  D/HdmiHelper:     - 1 | true | 0  
  </code></pre>
  
  <ul>
  <li><strong>Question 1:</strong></li>
  </ul>
  
  <p>I received true: which means that tv is on what is true. If the tv is off i receive false. That seems to work.</p>
  
  <p>Though, I was expecting to receive a callback every time I change the tv state, which is not happening. Any idea?</p>
  
  <ul>
  <li><strong>Question 2:</strong></li>
  </ul>
  
  <p>continuing with the logs for the OneTouchPlayCallback:</p>
  
  <pre><code>D/HdmiHelper: Start method onComplete | class $Proxy1 | interface android.hardware.hdmi.HdmiPlaybackClient$OneTouchPlayCallback
  D/HdmiHelper: onComplete: 2
  </code></pre>
  
  <p>Looking into the class <a href="https://chromium.googlesource.com/android_tools/+/78ccfd5f7e4880597fd90c61453a3be0e7aee5f0/sdk/sources/android-21/android/hardware/hdmi/HdmiPlaybackClient.java" rel="noreferrer">HdmiPlaybackClient.java</a> if everything went good the answer would be 0 (@param result the result of the operation. {@link HdmiControlManager#RESULT_SUCCESS . You can find this variable in <a href="https://chromium.googlesource.com/android_tools/+/8e0c4b2a99e3d07ad7412a5e817f24ff4a3fe0cc/sdk/sources/android-25/android/hardware/hdmi/HdmiControlManager.java" rel="noreferrer">HdmiControlManager.java</a> class}. Instead, I receive 2 which i assume that is RESULT_SOURCE_NOT_AVAILABLE.</p>
  
  <p>Any idea why?</p>
  
  <ul>
  <li><strong>Question 3</strong></li>
  </ul>
  
  <p>Continuing now with the logs for the DisplayStatusCallback:</p>
  
  <pre><code>D/HdmiHelper: Start method onComplete | class $Proxy3 | interface android.hardware.hdmi.HdmiPlaybackClient$DisplayStatusCallback
  D/HdmiHelper: onComplete: 2
  </code></pre>
  
  <p>According to the definition of this callback:</p>
  
  <pre><code>/**
       * Listener used by the client to get display device status.
       */
      public interface DisplayStatusCallback {
          /**
           * Called when display device status is reported.
           *
           * @param status display device status. It should be one of the following values.
           *            &lt;ul&gt;
           *            &lt;li&gt;{@link HdmiControlManager#POWER_STATUS_ON}
           *            &lt;li&gt;{@link HdmiControlManager#POWER_STATUS_STANDBY}
           *            &lt;li&gt;{@link HdmiControlManager#POWER_STATUS_TRANSIENT_TO_ON}
           *            &lt;li&gt;{@link HdmiControlManager#POWER_STATUS_TRANSIENT_TO_STANDBY}
           *            &lt;li&gt;{@link HdmiControlManager#POWER_STATUS_UNKNOWN}
           *            &lt;/ul&gt;
           */
          public void onComplete(int status);
      }
  </code></pre>
  
  <p>and looking into the HdmiControlManager I receive 2 wich means that is:</p>
  
  <pre><code>public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
  </code></pre>
  
  <p>Which is a strange result because that is not what's happening. </p>
  
  <ul>
  <li><strong>Continuing with logs for your information:</strong></li>
  </ul>
  
  <p>Answer for: </p>
  
  <p>getActiveSource is null</p>
  
  <p>I also tested this code that calls the getTvClient() method:</p>
  
  <pre><code>Method method_getTvClient = obj_HdmiControlManager.getClass().getMethod("getTvClient");
  Object obj_HdmiTvClient = method_getTvClient.invoke( obj_HdmiControlManager );
  Log.d("HdmiHelper", "obj_HdmiTvClient: " + obj_HdmiTvClient);
  </code></pre>
  
  <p>and the result is null.</p>
  
  <p>I also tried the approach of sending a vendor command following <a href="http://www.cec-o-matic.com" rel="noreferrer">CEC-O-MATIC website</a> but I was unable to have success. If you have any instructions about this, please give me some directions and I will test it.</p>
  
  <p><strong>LibCEC approach:</strong></p>
  
  <p>I was able to cross compile libcec to android thanks to this <a href="https://community.xibo.org.uk/t/hdmi-cec-control/5202/15" rel="noreferrer">post</a>. But libcec is always answering to me  "command 'PING' was not acked by the controller".</p>
  
  <p>I've added the flags -DHAVE_EXYNOS_API=1 and -DHAVE_AOCEC_API=1 to libcec.</p>
  
  <p><strong>System Information</strong></p>
  
  <p>The device /dev/cec is settled:</p>
  
  <pre><code>q8723bs:/ # ls -l /dev/cec                                                                                                                                              
  crw-rw-rw- 1 root root 218,   0 2017-12-19 16:33 /dev/cec
  </code></pre>
  
  <p>I also can find it on /sys/class/cec:</p>
  
  <pre><code>q8723bs:/ # ls -laht /sys/class/cec/                                                                                                                                    
  total 0
  -r--r--r--   1 root root 4.0K 2017-12-19 16:45 arc_port
  lrwxrwxrwx   1 root root    0 2017-12-19 16:45 cec -&gt; ../../devices/aocec/cec
  -r--r--r--   1 root root 4.0K 2017-12-19 16:45 cec_version
  --w-------   1 root root 4.0K 2017-12-19 16:45 cmd
  -rw-rw-r--   1 root root 4.0K 2017-12-19 16:45 dbg_en
  -rw-rw-r--   1 root root 4.0K 2017-12-19 16:45 device_type
  -r--r--r--   1 root root 4.0K 2017-12-19 16:45 dump_reg
  -rw-rw-r--   1 root root 4.0K 2017-12-19 16:45 fun_cfg
  -rw-rw-r--   1 root root 4.0K 2017-12-19 16:45 menu_language
  -r--r--r--   1 root root 4.0K 2017-12-19 16:45 osd_name
  -rw-rw-r--   1 root root 4.0K 2017-12-19 16:45 physical_addr
  -r--r--r--   1 root root 4.0K 2017-12-19 16:45 pin_status
  -r--r--r--   1 root root 4.0K 2017-12-19 16:45 port_num
  -rw-rw-r--   1 root root 4.0K 2017-12-19 16:45 port_seq
  -r--r--r--   1 root root 4.0K 2017-12-19 16:45 port_status
  -rw-rw-r--   1 root root 4.0K 2017-12-19 16:45 vendor_id
  -r--r--r--   1 root root 4.0K 2017-12-19 16:45 wake_up
  </code></pre>
  
  <p>But when I ran cec-client I receive this answer:</p>
  
  <pre><code>q8723bs:/ # id
  uid=0(root) gid=0(root) groups=0(root) context=u:r:toolbox:s0
  q8723bs:/ # cec-client -s /dev/cec                                                                                                                                      
  opening a connection to the CEC adapter...
  DEBUG:   [               1] Broadcast (F): osd name set to 'Broadcast'
  DEBUG:   [               2] connection opened, clearing any previous input and waiting for active transmissions to end before starting
  DEBUG:   [             396] communication thread started
  DEBUG:   [            1396] command 'PING' was not acked by the controller
  </code></pre>
  
  <p>As a note, I also have the device /dev/input/event2 that is a read only cec_input:</p>
  
  <pre><code>q8723bs:/ # ls -l /dev/input/event2                                                                                                                                     
  crw-rw---- 1 root input 13,  66 2017-12-19 16:33 /dev/input/event2
  q8723bs:/ # ls /sys/devices/virtual/input/input2/                                                                                                                      
  capabilities/  event2/        id/            modalias       name           phys           power/         properties     subsystem/     uevent         uniq
  q8723bs:/ # cat /sys/devices/virtual/input/input2/name                                                                                                                  
  cec_input
  </code></pre>
  
  <p>I tried to run it on /dev/input/event2 but obviously it didn't work because it could not open a connection:</p>
  
  <pre><code>q8723bs:/ # cec-client /dev/input/event2                                                                                                                                
  No device type given. Using 'recording device'
  CEC Parser created - libCEC version 4.0.2
  opening a connection to the CEC adapter...
  DEBUG:   [               1] Broadcast (F): osd name set to 'Broadcast'
  ERROR:   [            3335] error opening serial port '/dev/input/event2': Couldn't lock the serial port
  ERROR:   [            3335] could not open a connection (try 1)
  </code></pre>
  
  <p><strong>in Summary:</strong></p>
  
  <p>I could not get the command to turn on or change the input source of tv working in either of the cases. Any direction would be very helpful. Thanks in advance.</p>
  
  <p>note: I was able to accomplish it with libcec and raspberry pi on the same tv</p>