Merge changes Ie889f696,Ie3d7189b,Ibeceb4c4,I08055c7e,I7b01513b, ...

* changes:
  playback device should handle the active source claiming when it exists.
  Set local active port to HOME when oneTouchPlay is triggered. Update local active source at the same time.
  Add ro.hdmi.property_hdmi_cec_never_assign_logical_addresses to skip the logical addresses that will not be assigned.
  Fix routing logic on handling routing change/info.
  Add routingChange and routingInformation handlers.
  Add setStreamPath handlers and do input switching according to the new active path.
  Add ActiveSouce handling logic to switch to ARC input when the new Active is not under the current device.
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 1b0eb19..6f5a196 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -256,6 +256,41 @@
     static final int USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON = 1;
     static final int NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON = 2;
 
+    // Port id to record local active port for Routing Control features
+    // They are used to map to corresponding Inputs
+    // Current interface is only implemented for specific device.
+    // Developers can add more port number and map them to corresponding inputs on demand.
+    @IntDef({
+        CEC_SWITCH_HOME,
+        CEC_SWITCH_HDMI1,
+        CEC_SWITCH_HDMI2,
+        CEC_SWITCH_HDMI3,
+        CEC_SWITCH_HDMI4,
+        CEC_SWITCH_HDMI5,
+        CEC_SWITCH_HDMI6,
+        CEC_SWITCH_HDMI7,
+        CEC_SWITCH_HDMI8,
+        CEC_SWITCH_ARC,
+        CEC_SWITCH_BLUETOOTH,
+        CEC_SWITCH_OPTICAL,
+        CEC_SWITCH_AUX
+    })
+    @interface LocalActivePort {}
+    static final int CEC_SWITCH_HOME = 0;
+    static final int CEC_SWITCH_HDMI1 = 1;
+    static final int CEC_SWITCH_HDMI2 = 2;
+    static final int CEC_SWITCH_HDMI3 = 3;
+    static final int CEC_SWITCH_HDMI4 = 4;
+    static final int CEC_SWITCH_HDMI5 = 5;
+    static final int CEC_SWITCH_HDMI6 = 6;
+    static final int CEC_SWITCH_HDMI7 = 7;
+    static final int CEC_SWITCH_HDMI8 = 8;
+    static final int CEC_SWITCH_ARC = 17;
+    static final int CEC_SWITCH_BLUETOOTH = 18;
+    static final int CEC_SWITCH_OPTICAL = 19;
+    static final int CEC_SWITCH_AUX = 20;
+    static final int CEC_SWITCH_PORT_MAX = 21;
+
     static final String PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM =
             "persist.sys.hdmi.addr.audiosystem";
     static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
@@ -274,6 +309,13 @@
     static final String PROPERTY_SET_MENU_LANGUAGE = "ro.hdmi.set_menu_language";
 
     /**
+     * Property to save the ARC port id on system audio device.
+     * <p>When ARC is initiated, this port will be used to turn on ARC.
+     */
+    static final String PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT =
+            "ro.hdmi.property_sytem_audio_device_arc_port";
+
+    /**
      * Property to disable muting logic in System Audio Control handling. Default is true.
      *
      * <p>True means enabling muting logic.
@@ -291,6 +333,24 @@
     static final String PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS =
             "ro.hdmi.property_hdmi_cec_never_claim_playback_logical_address";
 
+    /**
+     * A comma separated list of logical addresses that HdmiControlService
+     * will never assign local CEC devices to.
+     *
+     * <p> This is useful when HDMI CEC hardware module can't assign multiple logical addresses
+     * in the range same range of 0-7 or 8-15.
+     */
+    static final String PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES =
+            "ro.hdmi.property_hdmi_cec_never_assign_logical_addresses";
+
+    /**
+     * Property to indicate if the current device is a cec switch device.
+     *
+     * <p> Default is false.
+     */
+    static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH =
+            "ro.hdmi.property_is_device_hdmi_cec_switch";
+
     // Set to false to allow playback device to go to suspend mode even
     // when it's an active source. True by default.
     static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake";
@@ -331,13 +391,6 @@
             "persist.sys.hdmi.property_sytem_audio_mode_audio_port";
 
     /**
-     * Property to save the ARC port id on system audio device.
-     * <p>When ARC is initiated, this port will be used to turn on ARC.
-     */
-    static final String PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT =
-            "persist.sys.hdmi.property_sytem_audio_device_arc_port";
-
-    /**
      * Property to indicate if a CEC audio device should forward volume keys when system audio mode
      * is off.
      *
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index de6cdd4..e777ce8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -22,20 +22,25 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
+import android.os.SystemProperties;
 import android.util.Slog;
 import android.util.SparseArray;
+
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+
+import libcore.util.EmptyArray;
+
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.function.Predicate;
 import java.util.concurrent.ArrayBlockingQueue;
-import libcore.util.EmptyArray;
+import java.util.function.Predicate;
+
 import sun.util.locale.LanguageTag;
 
 /**
@@ -112,10 +117,18 @@
 
     private final NativeWrapper mNativeWrapperImpl;
 
+    /** List of logical addresses that should not be assigned to the current device.
+     *
+     * <p>Parsed from {@link Constants#PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES}
+     */
+    private final List<Integer> mNeverAssignLogicalAddresses;
+
     // Private constructor.  Use HdmiCecController.create().
     private HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper) {
         mService = service;
         mNativeWrapperImpl = nativeWrapper;
+        mNeverAssignLogicalAddresses = mService.getIntList(SystemProperties.get(
+            Constants.PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES));
     }
 
     /**
@@ -208,7 +221,8 @@
         for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
             int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
             if (curAddress != Constants.ADDR_UNREGISTERED
-                    && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) {
+                    && deviceType == HdmiUtils.getTypeFromAddress(curAddress)
+                    && !mNeverAssignLogicalAddresses.contains(curAddress)) {
                 boolean acked = false;
                 for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) {
                     if (sendPollMessage(curAddress, curAddress, 1)) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index c338e21..528e0a4 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.hdmi.Constants.LocalActivePort;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
 
@@ -1051,7 +1052,7 @@
      * <p>This check assumes target address is valid.
      * @param targetPhysicalAddress is the physical address of the target device
      * @return
-     * <p>If the target device is under the current device, return the port number of current device
+     * If the target device is under the current device, return the port number of current device
      * that the target device is connected to.
      *
      * <p>If the target device has the same physical address as the current device, return
@@ -1085,4 +1086,27 @@
         }
         return TARGET_NOT_UNDER_LOCAL_DEVICE;
     }
+
+    /** Calculates the physical address for {@code activePortId}.
+     *
+     * <p>This method assumes current device physical address is valid.
+     * <p>If the current device is already the leaf of the whole CEC system
+     * and can't have devices under it, will return its own physical address.
+     *
+     * @param activePortId is the local active port Id
+     * @return the calculated physical address of the port
+     */
+    protected int getActivePathOnSwitchFromActivePortId(@LocalActivePort int activePortId) {
+        int myPhysicalAddress = mService.getPhysicalAddress();
+        int finalMask = activePortId << 8;
+        int mask;
+        for (mask = 0x0F00; mask > 0x000F;  mask >>= 4) {
+            if ((myPhysicalAddress & mask) == 0)  {
+                break;
+            } else {
+                finalMask >>= 4;
+            }
+        }
+        return finalMask | myPhysicalAddress;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 20908b6..048a0e4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -20,10 +20,12 @@
 import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
 
 import android.annotation.Nullable;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioSystem;
+import android.media.tv.TvContract;
 import android.os.SystemProperties;
 import android.provider.Settings.Global;
 
@@ -32,6 +34,8 @@
 import com.android.server.hdmi.Constants.AudioCodec;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 
+import java.util.HashMap;
+
 /**
  * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
  * system.
@@ -60,16 +64,16 @@
     // AVR as audio receiver.
     @ServiceThreadOnly private boolean mArcEstablished = false;
 
-    /**
-     * Return value of {@link #getLocalPortFromPhysicalAddress(int)}
-     */
-    private static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1;
-    private static final int TARGET_SAME_PHYSICAL_ADDRESS = 0;
+    // If the current device uses TvInput for ARC. We assume all other inputs also use TvInput
+    // when ARC is using TvInput.
+    private boolean mArcIntentUsed = SystemProperties
+            .get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, "0").contains("tvinput");
 
-    // Local active port number used for Routing Control.
-    // Default 0 means HOME is the current active path. Temp solution only.
-    // TODO(amyjojo): adding system constants for Atom inputs port and TIF mapping.
-    private int mLocalActivePath = 0;
+    // Keeps the mapping (HDMI port ID to TV input URI) to keep track of the TV inputs ready to
+    // accept input switching request from HDMI devices. Requests for which the corresponding
+    // input ID is not yet registered by TV input framework need to be buffered for delayed
+    // processing.
+    private final HashMap<Integer, String> mTvInputs = new HashMap<>();
 
     protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
@@ -81,6 +85,13 @@
                 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true);
         mAutoTvOff = mService.readBooleanSetting(
                 Global.HDMI_CONTROL_AUTO_TV_OFF_ENABLED, true);
+        // TODO(amyjojo): make the map ro property.
+        mTvInputs.put(Constants.CEC_SWITCH_HDMI1,
+                "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
+        mTvInputs.put(Constants.CEC_SWITCH_HDMI2,
+                "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6");
+        mTvInputs.put(Constants.CEC_SWITCH_HDMI3,
+                "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
     }
 
     @Override
@@ -595,4 +606,135 @@
         assertRunOnServiceThread();
         mAutoDeviceOff = autoDeviceOff;
     }
+
+    @Override
+    protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
+        int port = getLocalPortFromPhysicalAddress(physicalAddress);
+        // Wake up if the new Active Source is the current device or under it
+        // or if System Audio Control is enabled.
+        if ((isSystemAudioActivated() || port >= 0) && mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+
+        if (isSystemAudioActivated() && port < 0) {
+            // If system audio mode is on and the new active source is not under the current device,
+            // Will switch to ARC input.
+            // TODO(b/115637145): handle system aduio without ARC
+            routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
+        } else if (mIsSwitchDevice && port >= 0) {
+            // If current device is a switch and the new active source is under it,
+            // will switch to the corresponding active path.
+            routeToInputFromPortId(port);
+        }
+    }
+
+    protected void routeToInputFromPortId(int portId) {
+        if (mArcIntentUsed) {
+            routeToTvInputFromPortId(portId);
+        } else {
+            // TODO(): implement input switching for devices not using TvInput.
+        }
+    }
+
+    protected void routeToTvInputFromPortId(int portId) {
+        if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) {
+            HdmiLogger.debug("Invalid port number for Tv Input switching.");
+            return;
+        }
+        // TODO(amyjojo): handle if switching to the current input
+        if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
+            switchToHomeTvInput();
+        } else if (portId == Constants.CEC_SWITCH_ARC) {
+            switchToTvInput(SystemProperties.get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT));
+            return;
+        } else {
+            String uri = mTvInputs.get(portId);
+            if (uri != null) {
+                switchToTvInput(mTvInputs.get(portId));
+            } else {
+                HdmiLogger.debug("Port number does not match any Tv Input.");
+                return;
+            }
+        }
+
+        setLocalActivePort(portId);
+    }
+
+    // For device to switch to specific TvInput with corresponding URI.
+    private void switchToTvInput(String uri) {
+        mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
+                TvContract.buildChannelUriForPassthroughInput(uri))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
+
+    // For device using TvInput to switch to Home.
+    private void switchToHomeTvInput() {
+        Intent activityIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+                        | Intent.FLAG_ACTIVITY_SINGLE_TOP
+                        | Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_NO_ANIMATION);
+        mService.getContext().startActivity(activityIntent);
+    }
+
+    @Override
+    protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
+        int port = getLocalPortFromPhysicalAddress(physicalAddress);
+        // Routing change or information sent from switches under the current device can be ignored.
+        if (port > 0) {
+            return;
+        }
+        // When other switches route to some other devices not under the current device,
+        // check system audio mode status and do ARC switch if needed.
+        if (port < 0 && isSystemAudioActivated()) {
+            handleRoutingChangeAndInformationForSystemAudio();
+            return;
+        }
+        // When other switches route to the current device
+        // and the current device is also a switch.
+        if (port == 0) {
+            handleRoutingChangeAndInformationForSwitch(message);
+        }
+    }
+
+    // Handle the system audio(ARC) part of the logic on receiving routing change or information.
+    private void handleRoutingChangeAndInformationForSystemAudio() {
+        if (mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+        // TODO(b/115637145): handle system aduio without ARC
+        routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
+    }
+
+    // Handle the routing control part of the logic on receiving routing change or information.
+    private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) {
+        if (mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+        if (getLocalActivePort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
+            routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
+            if (mService.playback() != null) {
+                mService.playback().setAndBroadcastActiveSource(
+                        message, mService.getPhysicalAddress());
+            } else {
+                setAndBroadcastActiveSource(message, mService.getPhysicalAddress());
+            }
+            return;
+        }
+
+        int routingInformationPath =
+                getActivePathOnSwitchFromActivePortId(getLocalActivePort());
+        // If current device is already the leaf of the whole HDMI system, will do nothing.
+        if (routingInformationPath == mService.getPhysicalAddress()) {
+            HdmiLogger.debug("Current device can't assign valid physical address"
+                    + "to devices under it any more. "
+                    + "It's physical address is " + routingInformationPath);
+            return;
+        }
+        // Otherwise will switch to the current active port and broadcast routing information.
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingInformation(
+                mAddress, routingInformationPath));
+        routeToInputFromPortId(getLocalActivePort());
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index e9dd682..379cc16 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -266,7 +266,8 @@
         setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
     }
 
-    private void wakeUpIfActiveSource() {
+    @Override
+    protected void wakeUpIfActiveSource() {
         if (!mIsActiveSource) {
             return;
         }
@@ -277,7 +278,8 @@
         }
     }
 
-    private void maySendActiveSource(int dest) {
+    @Override
+    protected void maySendActiveSource(int dest) {
         if (mIsActiveSource) {
             mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
                     mAddress, mService.getPhysicalAddress()));
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index fed6622..8d55299 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -19,8 +19,12 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.hdmi.Constants.LocalActivePort;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 
 import java.util.List;
@@ -35,6 +39,20 @@
     // Indicate if current device is Active Source or not
     private boolean mIsActiveSource = false;
 
+    // Device has cec switch functionality or not.
+    // Default is false.
+    protected boolean mIsSwitchDevice = SystemProperties.getBoolean(
+            Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
+
+    // Local active port number used for Routing Control.
+    // This records the default active port or the previous valid active port.
+    // Default is HOME input.
+    // Note that we don't save active path here because for source device,
+    // new Active Source physical address might not match the local active path
+    @GuardedBy("mLock")
+    @LocalActivePort
+    private int mLocalActivePort = Constants.CEC_SWITCH_HOME;
+
     protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
         super(service, deviceType);
     }
@@ -99,6 +117,7 @@
             setActiveSource(activeSource);
         }
         setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
+        switchInputOnReceivingNewActivePath(physicalAddress);
         return true;
     }
 
@@ -106,16 +125,130 @@
     @ServiceThreadOnly
     protected boolean handleRequestActiveSource(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        if (mIsActiveSource) {
-            mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
-                    mAddress, mService.getPhysicalAddress()));
-        }
+        maySendActiveSource(message.getSource());
         return true;
     }
 
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleSetStreamPath(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+        // If current device is the target path, set to Active Source.
+        // If the path is under the current device, should switch
+        if (physicalAddress == mService.getPhysicalAddress() && mService.isPlaybackDevice()) {
+            setAndBroadcastActiveSource(message, physicalAddress);
+        }
+        switchInputOnReceivingNewActivePath(physicalAddress);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleRoutingChange(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
+        // if the current device is a pure playback device
+        if (!mIsSwitchDevice
+                && newPath == mService.getPhysicalAddress()
+                && mService.isPlaybackDevice()) {
+            setAndBroadcastActiveSource(message, newPath);
+        }
+        handleRoutingChangeAndInformation(newPath, message);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleRoutingInformation(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+        // if the current device is a pure playback device
+        if (!mIsSwitchDevice
+                && physicalAddress == mService.getPhysicalAddress()
+                && mService.isPlaybackDevice()) {
+            setAndBroadcastActiveSource(message, physicalAddress);
+        }
+        handleRoutingChangeAndInformation(physicalAddress, message);
+        return true;
+    }
+
+    // Method to switch Input with the new Active Path.
+    // All the devices with Switch functionality should implement this.
+    protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
+        // do nothing
+    }
+
+    // Source device with Switch functionality should implement this method.
+    // TODO(): decide which type will handle the routing when multi device type is supported
+    protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
+        // do nothing
+    }
+
+    // Active source claiming needs to be handled in the parent class
+    // since we decide who will be the active source when the device supports
+    // multiple device types in this method.
+    // This method should only be called when the device can be the active source.
+    protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) {
+        // If the device has both playback and audio system logical addresses,
+        // playback will claim active source. Otherwise audio system will.
+        HdmiCecLocalDevice deviceToBeActiveSource = mService.playback();
+        if (deviceToBeActiveSource == null) {
+            deviceToBeActiveSource = mService.audioSystem();
+        }
+        if (this == deviceToBeActiveSource) {
+            ActiveSource activeSource = ActiveSource.of(mAddress, physicalAddress);
+            setIsActiveSource(true);
+            setActiveSource(activeSource);
+            wakeUpIfActiveSource();
+            maySendActiveSource(message.getSource());
+        }
+    }
+
     @ServiceThreadOnly
     void setIsActiveSource(boolean on) {
         assertRunOnServiceThread();
         mIsActiveSource = on;
     }
+
+    @ServiceThreadOnly
+    // Check if current device is the Active Source
+    boolean isActiveSource() {
+        assertRunOnServiceThread();
+        return mIsActiveSource;
+    }
+
+    protected void wakeUpIfActiveSource() {
+        if (!mIsActiveSource) {
+            return;
+        }
+        // Wake up the device if the power is in standby mode
+        if (mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+        return;
+    }
+
+    protected void maySendActiveSource(int dest) {
+        if (mIsActiveSource) {
+            mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
+                    mAddress, mService.getPhysicalAddress()));
+        }
+    }
+
+    @VisibleForTesting
+    protected void setLocalActivePort(@LocalActivePort int portId) {
+        synchronized (mLock) {
+            mLocalActivePort = portId;
+        }
+    }
+
+    // To get the local active port to switch to
+    // when receivng routing change or information.
+    @LocalActivePort
+    protected int getLocalActivePort() {
+        synchronized (mLock) {
+            return mLocalActivePort;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 7e369598..903045d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -417,7 +417,7 @@
         mSettingsObserver = new SettingsObserver(mHandler);
     }
 
-    private static List<Integer> getIntList(String string) {
+    protected static List<Integer> getIntList(String string) {
         ArrayList<Integer> list = new ArrayList<>();
         TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
         splitter.setString(string);
@@ -2114,11 +2114,15 @@
         return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
     }
 
+    boolean isPlaybackDevice() {
+        return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
+    }
+
     boolean isTvDeviceEnabled() {
         return isTvDevice() && tv() != null;
     }
 
-    private HdmiCecLocalDevicePlayback playback() {
+    protected HdmiCecLocalDevicePlayback playback() {
         return (HdmiCecLocalDevicePlayback)
                 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
     }
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 48c71d8..46611dd 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -85,7 +85,11 @@
     private void broadcastActiveSource() {
         sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath()));
         // Because only source device can create this action, it's safe to cast.
-        source().setIsActiveSource(true);
+        HdmiCecLocalDeviceSource source = source();
+        source.setIsActiveSource(true);
+        source.setActiveSource(getSourceAddress(), getSourcePath());
+        // Set local active port to HOME when One Touch Play.
+        source.setLocalActivePort(Constants.CEC_SWITCH_HOME);
     }
 
     private void queryDevicePowerStatus() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 7049b21..c442ae8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -17,6 +17,7 @@
 
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -53,6 +54,7 @@
     private HdmiControlService mHdmiControlService;
     private HdmiCecController mHdmiCecController;
     private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
+    private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
     private FakeNativeWrapper mNativeWrapper;
     private Looper mMyLooper;
     private TestLooper mTestLooper = new TestLooper();
@@ -133,7 +135,9 @@
 
         mMyLooper = mTestLooper.getLooper();
         mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+        mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
         mHdmiCecLocalDeviceAudioSystem.init();
+        mHdmiCecLocalDevicePlayback.init();
         mHdmiControlService.setIoLooper(mMyLooper);
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController =
@@ -142,6 +146,7 @@
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
         mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
+        mLocalDevices.add(mHdmiCecLocalDevicePlayback);
         mHdmiControlService.initPortInfo();
         // No TV device interacts with AVR so system audio control won't be turned on here
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -557,4 +562,63 @@
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
+
+    @Test
+    public void handleActiveSource_activeSourceFromTV_swithToArc() {
+        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+
+        ActiveSource expectedActiveSource = ActiveSource.of(ADDR_TV, 0x0000);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
+            .isEqualTo(expectedActiveSource);
+    }
+
+    @Test
+    public void handleRoutingChange_currentActivePortIsHome() {
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x3000, mAvrPhysicalAddress);
+
+        HdmiCecMessage expectedMessage =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_AUDIO_SYSTEM, mAvrPhysicalAddress);
+        ActiveSource expectedActiveSource = ActiveSource.of(ADDR_AUDIO_SYSTEM, mAvrPhysicalAddress);
+        int expectedLocalActivePort = Constants.CEC_SWITCH_HOME;
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
+            .isEqualTo(expectedActiveSource);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePort())
+            .isEqualTo(expectedLocalActivePort);
+        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+    }
+
+    @Test
+    public void handleRoutingInformation_currentActivePortIsHDMI1() {
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x2000);
+        mHdmiCecLocalDeviceAudioSystem.setLocalActivePort(Constants.CEC_SWITCH_HDMI1);
+        HdmiCecMessage expectedMessage =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_AUDIO_SYSTEM, 0x2100);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+    }
+
+    @Test
+    public void handleRoutingChange_homeIsActive_playbackSendActiveSource() {
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x2000);
+
+        HdmiCecMessage expectedMessage =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+    }
 }