Merge "Add SendMessageCallback to Hdmi control service."
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index 1bc278d..f8e9b7b 100644
--- a/services/core/java/com/android/server/hdmi/FeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -164,8 +164,13 @@
         mActionTimer.sendTimerMessage(state, delayMillis);
     }
 
-    protected final boolean sendCommand(HdmiCecMessage cmd) {
-        return mService.sendCecCommand(cmd);
+    protected final void sendCommand(HdmiCecMessage cmd) {
+        mService.sendCecCommand(cmd);
+    }
+
+    protected final void sendCommand(HdmiCecMessage cmd,
+            HdmiControlService.SendMessageCallback callback) {
+        mService.sendCecCommand(cmd, callback);
     }
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index f99a01d..662159d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -20,9 +20,6 @@
 import android.hardware.hdmi.HdmiCecDeviceInfo;
 import android.hardware.hdmi.HdmiCecMessage;
 import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
@@ -176,13 +173,59 @@
      *                         Otherwise, scan address will start from {@code preferredAddress}
      * @param callback callback interface to report allocated logical address to caller
      */
-    void allocateLogicalAddress(int deviceType, int preferredAddress,
-            AllocateLogicalAddressCallback callback) {
-        Message msg = mIoHandler.obtainMessage(MSG_ALLOCATE_LOGICAL_ADDRESS);
-        msg.arg1 = deviceType;
-        msg.arg2 = preferredAddress;
-        msg.obj = callback;
-        mIoHandler.sendMessage(msg);
+    void allocateLogicalAddress(final int deviceType, final int preferredAddress,
+            final AllocateLogicalAddressCallback callback) {
+        runOnIoThread(new Runnable() {
+            @Override
+            public void run() {
+                handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
+            }
+        });
+    }
+
+    private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
+            final AllocateLogicalAddressCallback callback) {
+        int startAddress = preferredAddress;
+        // If preferred address is "unregistered", start address will be the smallest
+        // address matched with the given device type.
+        if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
+            for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
+                if (deviceType == HdmiCec.getTypeFromAddress(i)) {
+                    startAddress = i;
+                    break;
+                }
+            }
+        }
+
+        int logicalAddress = HdmiCec.ADDR_UNREGISTERED;
+        // Iterates all possible addresses which has the same device type.
+        for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
+            int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
+            if (curAddress != HdmiCec.ADDR_UNREGISTERED
+                    && deviceType == HdmiCec.getTypeFromAddress(i)) {
+                // <Polling Message> is a message which has empty body and
+                // uses same address for both source and destination address.
+                // If sending <Polling Message> failed (NAK), it becomes
+                // new logical address for the device because no device uses
+                // it as logical address of the device.
+                int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress,
+                        EMPTY_BODY);
+                if (error != ERROR_SUCCESS) {
+                    logicalAddress = curAddress;
+                    break;
+                }
+            }
+        }
+
+        final int assignedAddress = logicalAddress;
+        if (callback != null) {
+            runOnServiceThread(new Runnable() {
+                    @Override
+                public void run() {
+                    callback.onAllocated(deviceType, assignedAddress);
+                }
+            });
+        }
     }
 
     private static byte[] buildBody(int opcode, byte[] params) {
@@ -192,101 +235,6 @@
         return body;
     }
 
-    private final class IoHandler extends Handler {
-        private IoHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_SEND_CEC_COMMAND:
-                    HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj;
-                    byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
-                    nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
-                            cecMessage.getDestination(), body);
-                    break;
-                case MSG_ALLOCATE_LOGICAL_ADDRESS:
-                    int deviceType = msg.arg1;
-                    int preferredAddress = msg.arg2;
-                    AllocateLogicalAddressCallback callback =
-                            (AllocateLogicalAddressCallback) msg.obj;
-                    handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
-                    break;
-                default:
-                    Slog.w(TAG, "Unsupported CEC Io request:" + msg.what);
-                    break;
-            }
-        }
-
-        private void handleAllocateLogicalAddress(int deviceType, int preferredAddress,
-                AllocateLogicalAddressCallback callback) {
-            int startAddress = preferredAddress;
-            // If preferred address is "unregistered", start_index will be the smallest
-            // address matched with the given device type.
-            if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
-                for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
-                    if (deviceType == HdmiCec.getTypeFromAddress(i)) {
-                        startAddress = i;
-                        break;
-                    }
-                }
-            }
-
-            int logcialAddress = HdmiCec.ADDR_UNREGISTERED;
-            // Iterates all possible addresses which has the same device type.
-            for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
-                int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
-                if (curAddress != HdmiCec.ADDR_UNREGISTERED
-                        && deviceType == HdmiCec.getTypeFromAddress(i)) {
-                    // <Polling Message> is a message which has empty body and
-                    // uses same address for both source and destination address.
-                    // If sending <Polling Message> failed (NAK), it becomes
-                    // new logical address for the device because no device uses
-                    // it as logical address of the device.
-                    int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress,
-                            EMPTY_BODY);
-                    if (error != ERROR_SUCCESS) {
-                        logcialAddress = curAddress;
-                        break;
-                    }
-                }
-            }
-
-            Message msg = mControlHandler.obtainMessage(MSG_REPORT_LOGICAL_ADDRESS);
-            msg.arg1 = deviceType;
-            msg.arg2 = logcialAddress;
-            msg.obj = callback;
-            mControlHandler.sendMessage(msg);
-        }
-    }
-
-    private final class ControlHandler extends Handler {
-        private ControlHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_RECEIVE_CEC_COMMAND:
-                    // TODO: delegate it to HdmiControl service.
-                    onReceiveCommand((HdmiCecMessage) msg.obj);
-                    break;
-                case MSG_REPORT_LOGICAL_ADDRESS:
-                    int deviceType = msg.arg1;
-                    int logicalAddress = msg.arg2;
-                    AllocateLogicalAddressCallback callback =
-                            (AllocateLogicalAddressCallback) msg.obj;
-                    callback.onAllocated(deviceType, logicalAddress);
-                    break;
-                default:
-                    Slog.i(TAG, "Unsupported message type:" + msg.what);
-                    break;
-            }
-        }
-    }
-
     /**
      * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
      * logical address as new device info's.
@@ -411,10 +359,18 @@
         return nativeGetVendorId(mNativePtr);
     }
 
+    private void runOnIoThread(Runnable runnable) {
+        mIoHandler.post(runnable);
+    }
+
+    private void runOnServiceThread(Runnable runnable) {
+        mControlHandler.post(runnable);
+    }
+
     private void init(HdmiControlService service, long nativePtr) {
         mService = service;
-        mIoHandler = new IoHandler(service.getServiceLooper());
-        mControlHandler = new ControlHandler(service.getServiceLooper());
+        mIoHandler = new Handler(service.getServiceLooper());
+        mControlHandler = new Handler(service.getServiceLooper());
         mNativePtr = nativePtr;
     }
 
@@ -438,13 +394,27 @@
         HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand
                 (sourceAddress, message.getSource(), message.getOpcode(),
                         HdmiCecMessageBuilder.ABORT_REFUSED);
-        sendCommand(cecMessage);
-
+        sendCommand(cecMessage, null);
     }
 
-    boolean sendCommand(HdmiCecMessage cecMessage) {
-        Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
-        return mIoHandler.sendMessage(message);
+    void sendCommand(final HdmiCecMessage cecMessage,
+            final HdmiControlService.SendMessageCallback callback) {
+        runOnIoThread(new Runnable() {
+            @Override
+            public void run() {
+                byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
+                final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
+                        cecMessage.getDestination(), body);
+                if (callback != null) {
+                    runOnServiceThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            callback.onSendCompleted(error);
+                        }
+                    });
+                }
+            }
+        });
     }
 
     /**
@@ -453,12 +423,15 @@
     private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
         byte opcode = body[0];
         byte params[] = Arrays.copyOfRange(body, 1, body.length);
-        HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
+        final HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
 
         // Delegate message to main handler so that it handles in main thread.
-        Message message = mControlHandler.obtainMessage(
-                MSG_RECEIVE_CEC_COMMAND, cecMessage);
-        mControlHandler.sendMessage(message);
+        runOnServiceThread(new Runnable() {
+            @Override
+            public void run() {
+                onReceiveCommand(cecMessage);
+            }
+        });
     }
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 09153b9..d7a2c1c6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -18,30 +18,25 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.hdmi.IHdmiControlCallback;
-import android.hardware.hdmi.IHdmiControlService;
-import android.hardware.hdmi.IHdmiHotplugEventListener;
 import android.hardware.hdmi.HdmiCec;
 import android.hardware.hdmi.HdmiCecDeviceInfo;
 import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.IHdmiControlService;
+import android.hardware.hdmi.IHdmiHotplugEventListener;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.util.SparseArray;
-
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.SystemService;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Locale;
 
 /**
@@ -54,6 +49,20 @@
     // TODO: Rename the permission to HDMI_CONTROL.
     private static final String PERMISSION = "android.permission.HDMI_CEC";
 
+    /**
+     * Interface to report send result.
+     */
+    interface SendMessageCallback {
+        /**
+         * Called when {@link HdmiControlService#sendCecCommand} is completed.
+         *
+         * @param error result of send request. 0 if succeed. Otherwise it will be
+         *        negative value
+         */
+        // TODO: define error code as constants and update javadoc.
+        void onSendCompleted(int error);
+    }
+
     // A thread to handle synchronous IO of CEC and MHL control service.
     // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
     // and sparse call it shares a thread to handle IO operations.
@@ -205,10 +214,14 @@
      * Transmit a CEC command to CEC bus.
      *
      * @param command CEC command to send out
-     * @return {@code true} if succeeds to send command
+     * @param callback interface used to the result of send command
      */
-    boolean sendCecCommand(HdmiCecMessage command) {
-        return mCecController.sendCommand(command);
+    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
+        mCecController.sendCommand(command, callback);
+    }
+
+    void sendCecCommand(HdmiCecMessage command) {
+        mCecController.sendCommand(command, null);
     }
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
index 0a701f9..db9d28d 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiCecMessage;
 import android.util.Slog;
 
 /**
@@ -37,17 +38,24 @@
 
     @Override
     boolean start() {
-        if (sendCommand(
-                HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress, mAvrAddress))) {
-            mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
-            addTimer(mState, TIMEOUT_MS);
-        } else {
-            Slog.w(TAG, "Failed to send <Request ARC Initiation>");
-            // If failed to send <Request ARC Initiation>, start "Disabled" ARC transmission
-            // action.
-            disableArcTransmission();
-            finish();
-        }
+        HdmiCecMessage command = HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress,
+                mAvrAddress);
+        sendCommand(command, new HdmiControlService.SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                // success.
+                if (error == 0) {
+                    mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
+                    addTimer(mState, TIMEOUT_MS);
+                } else {
+                    Slog.w(TAG, "Failed to send <Request ARC Initiation>");
+                    // If failed to send <Request ARC Initiation>, start "Disabled"
+                    // ARC transmission action.
+                    disableArcTransmission();
+                    finish();
+                }
+            }
+        });
         return true;
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
index db1b992..7669f87 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiCecMessage;
 import android.util.Slog;
 
 /**
@@ -37,17 +38,23 @@
 
     @Override
     boolean start() {
-        if (sendCommand(
-                HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress))) {
-            mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
-            addTimer(mState, TIMEOUT_MS);
-        } else {
-            Slog.w(TAG, "Failed to send <Request ARC Initiation>");
-            // If failed to send <Request ARC Termination>, start "Disabled" ARC transmission
-            // action.
-            disableArcTransmission();
-            finish();
-        }
+        HdmiCecMessage command =
+                HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress);
+        sendCommand(command, new HdmiControlService.SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error == 0) {
+                    mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
+                    addTimer(mState, TIMEOUT_MS);
+                } else {
+                    Slog.w(TAG, "Failed to send <Request ARC Initiation>");
+                    // If failed to send <Request ARC Termination>, start "Disabled" ARC
+                    // transmission action.
+                    disableArcTransmission();
+                    finish();
+                }
+            }
+        });
         return true;
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
index 6bf149b..94776a2 100644
--- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
+++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
@@ -64,26 +64,7 @@
     @Override
     boolean start() {
         if (mEnabled) {
-            if (sendCommand(
-                    HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress))) {
-                // Enable ARC status immediately after sending <Report Arc Initiated>.
-                // If AVR responds with <Feature Abort>, disable ARC status again.
-                // This is different from spec that says that turns ARC status to "Enabled"
-                // if <Report ARC Initiated> is acknowledged and no <Feature Abort> is received.
-                // But implemented this way to save the time having to wait for <Feature Abort>.
-                setArcStatus(true);
-                // If succeeds to send <Report ARC Initiated>, wait general timeout
-                // to check whether there is no <Feature Abort> for <Report ARC Initiated>.
-                mState = STATE_WAITING_TIMEOUT;
-                addTimer(mState, TIMEOUT_MS);
-            } else {
-                // If fails to send <Report ARC Initiated>, disable ARC and
-                // send <Report ARC Terminated> directly.
-                Slog.w(TAG, "Failed to send <Report ARC Initiated>:[source:" + mSourceAddress
-                        + ", avr Address:" + mAvrAddress + "]");
-                setArcStatus(false);
-                finish();
-            }
+            sendReportArcInitiated();
         } else {
             setArcStatus(false);
             finish();
@@ -91,6 +72,38 @@
         return true;
     }
 
+    private void sendReportArcInitiated() {
+        HdmiCecMessage command =
+                HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress);
+        sendCommand(command, new HdmiControlService.SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error == 0) {
+                    // Enable ARC status immediately after sending <Report Arc Initiated>.
+                    // If AVR responds with <Feature Abort>, disable ARC status again.
+                    // This is different from spec that says that turns ARC status to
+                    // "Enabled" if <Report ARC Initiated> is acknowledged and no
+                    // <Feature Abort> is received.
+                    // But implemented this way to save the time having to wait for
+                    // <Feature Abort>.
+                    setArcStatus(true);
+                    // If succeeds to send <Report ARC Initiated>, wait general timeout
+                    // to check whether there is no <Feature Abort> for <Report ARC Initiated>.
+                    mState = STATE_WAITING_TIMEOUT;
+                    addTimer(mState, TIMEOUT_MS);
+                } else {
+                    // If fails to send <Report ARC Initiated>, disable ARC and
+                    // send <Report ARC Terminated> directly.
+                    Slog.w(TAG, "Failed to send <Report ARC Initiated>:[source:"
+                            + mSourceAddress
+                            + ", avr Address:" + mAvrAddress + "]");
+                    setArcStatus(false);
+                    finish();
+                }
+            }
+        });
+    }
+
     private void setArcStatus(boolean enabled) {
         boolean wasEnabled = mService.setArcStatus(enabled);
         Slog.i(TAG, "Change arc status [old:" + wasEnabled + " ,new:" + enabled);