Handle TODO in SystemAudioActionFromTv.

- Added onFinishedCallback to handle the deferred starting of FeatureAction.

Bug: 15843078, Bug: 15841546
Change-Id: I6cb5fba91d6115e8bb02c38601a72ed315c22e2f
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index 7d15f4c..b7b2f90 100644
--- a/services/core/java/com/android/server/hdmi/FeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -18,11 +18,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -59,6 +61,8 @@
     // Timer that manages timeout events.
     protected ActionTimer mActionTimer;
 
+    private ArrayList<Pair<FeatureAction, Runnable>> mOnFinishedCallbacks;
+
     FeatureAction(HdmiCecLocalDevice source) {
         mSource = source;
         mService = mSource.getService();
@@ -220,8 +224,22 @@
      * Finish up the action. Reset the state, and remove itself from the action queue.
      */
     protected void finish() {
+        finish(true);
+    }
+
+    void finish(boolean removeSelf) {
         clear();
-        removeAction(this);
+        if (removeSelf) {
+            removeAction(this);
+        }
+        if (mOnFinishedCallbacks != null) {
+            for (Pair<FeatureAction, Runnable> actionCallbackPair: mOnFinishedCallbacks) {
+                if (actionCallbackPair.first.mState != STATE_NONE) {
+                    actionCallbackPair.second.run();
+                }
+            }
+            mOnFinishedCallbacks = null;
+        }
     }
 
     protected final HdmiCecLocalDevice localDevice() {
@@ -244,10 +262,17 @@
         return mSource.getDeviceInfo().getPhysicalAddress();
     }
 
-    protected void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
+    protected final void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
         sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
                 getSourceAddress(), targetAddress, uiCommand));
         sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
                 getSourceAddress(), targetAddress));
     }
+
+    protected final void addOnFinishedCallback(FeatureAction action, Runnable runnable) {
+        if (mOnFinishedCallbacks == null) {
+            mOnFinishedCallbacks = new ArrayList<>();
+        }
+        mOnFinishedCallbacks.add(Pair.create(action, runnable));
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 3937ce1..7515242 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -65,7 +65,7 @@
     // Note that access to this collection should happen in service thread.
     private final LinkedList<FeatureAction> mActions = new LinkedList<>();
 
-    private Handler mHandler = new Handler () {
+    private final Handler mHandler = new Handler () {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -482,6 +482,7 @@
     @ServiceThreadOnly
     void removeAction(final FeatureAction action) {
         assertRunOnServiceThread();
+        action.finish(false);
         mActions.remove(action);
         checkIfPendingActionsCleared();
     }
@@ -502,8 +503,8 @@
         while (iter.hasNext()) {
             FeatureAction action = iter.next();
             if (action != exception && action.getClass().equals(clazz)) {
-                action.clear();
-                mActions.remove(action);
+                action.finish(false);
+                iter.remove();
             }
         }
         checkIfPendingActionsCleared();
@@ -637,7 +638,7 @@
         Iterator<FeatureAction> iter = mActions.iterator();
         while (iter.hasNext()) {
             FeatureAction action = iter.next();
-            action.finish();
+            action.finish(false);
             iter.remove();
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 923b87d..8840c62 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -536,10 +536,17 @@
     }
 
     @ServiceThreadOnly
+    // Seq #32
     void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
+        if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
+            setSystemAudioMode(false, true);
+            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+            return;
+        }
         HdmiCecDeviceInfo avr = getAvrDeviceInfo();
         if (avr == null) {
+            setSystemAudioMode(false, true);
             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
             return;
         }
diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
index 185de59..6204c16 100644
--- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
+++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
@@ -219,7 +219,7 @@
             return;
         }
 
-        // Should ave only one Device Select Action
+        // Should have only one Device Select Action
         DeviceSelectAction action = actions.get(0);
         if (action.getTargetAddress() == address) {
             removeAction(DeviceSelectAction.class);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index 86895cc..4be036a 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -23,14 +23,20 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import java.util.List;
+
 /**
  * Base feature action class for SystemAudioActionFromTv and SystemAudioActionFromAvr.
  */
 abstract class SystemAudioAction extends FeatureAction {
     private static final String TAG = "SystemAudioAction";
 
+    // Transient state to differentiate with STATE_NONE where the on-finished callback
+    // will not be called.
+    private static final int STATE_CHECK_ROUTING_IN_PRGRESS = 1;
+
     // State in which waits for <SetSystemAudioMode>.
-    private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 1;
+    private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 2;
 
     private static final int MAX_SEND_RETRY_COUNT = 2;
 
@@ -65,7 +71,25 @@
         mCallback = callback;
     }
 
+    // Seq #27
     protected void sendSystemAudioModeRequest() {
+        mState = STATE_CHECK_ROUTING_IN_PRGRESS;
+        List<RoutingControlAction> routingActions = getActions(RoutingControlAction.class);
+        if (!routingActions.isEmpty()) {
+            // Should have only one Routing Control Action
+            RoutingControlAction routingAction = routingActions.get(0);
+            routingAction.addOnFinishedCallback(this, new Runnable() {
+                @Override
+                public void run() {
+                    sendSystemAudioModeRequestInternal();
+                }
+            });
+            return;
+        }
+        sendSystemAudioModeRequestInternal();
+    }
+
+    private void sendSystemAudioModeRequestInternal() {
         int avrPhysicalAddress = tv().getAvrDeviceInfo().getPhysicalAddress();
         HdmiCecMessage command = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
                 getSourceAddress(),
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index a565077..77783b7 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -23,7 +23,7 @@
 /**
  * Feature action that handles System Audio initiated by AVR devices.
  */
-// # Seq 33
+// Seq #33
 final class SystemAudioActionFromAvr extends SystemAudioAction {
     /**
      * Constructor
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
index 2146c4e..cb3588c 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
@@ -24,6 +24,7 @@
  * Feature action that handles System Audio initiated by TV devices.
  */
 final class SystemAudioActionFromTv extends SystemAudioAction {
+
     /**
      * Constructor
      *
@@ -41,9 +42,6 @@
 
     @Override
     boolean start() {
-        // TODO: Check HDMI-CEC is enabled.
-        // TODO: Move to the waiting state if currently a routing change is in progress.
-
         removeSystemAudioActionInProgress();
         sendSystemAudioModeRequest();
         return true;
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
index 80954d4..d03634e 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
@@ -21,6 +21,7 @@
 /**
  * Action to initiate system audio once AVR is detected on Device discovery action.
  */
+// Seq #27
 final class SystemAudioAutoInitiationAction extends FeatureAction {
     private final int mAvrAddress;