12918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim/*
22918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim * Copyright (C) 2014 The Android Open Source Project
32918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim *
42918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim * Licensed under the Apache License, Version 2.0 (the "License");
52918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim * you may not use this file except in compliance with the License.
62918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim * You may obtain a copy of the License at
72918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim *
82918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim *      http://www.apache.org/licenses/LICENSE-2.0
92918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim *
102918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim * Unless required by applicable law or agreed to in writing, software
112918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim * distributed under the License is distributed on an "AS IS" BASIS,
122918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim * See the License for the specific language governing permissions and
142918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim * limitations under the License.
152918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim */
162918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim
172918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kimpackage com.android.server.hdmi;
182918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim
19e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jangimport static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE;
20e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jangimport static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION;
21e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jangimport static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE;
2212e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jangimport static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED;
2312e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jangimport static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
2412e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jangimport static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN;
25339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jangimport static android.hardware.hdmi.HdmiControlManager.OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT;
26e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jangimport static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED;
27e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jangimport static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
28e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jangimport static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE;
29e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jangimport static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
30e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jangimport static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
31e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jangimport static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
3212e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang
33c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kimimport android.hardware.hdmi.HdmiControlManager;
345008486b09c588bf3409b70d9ee29225e8593c64Jinsuk Kimimport android.hardware.hdmi.HdmiDeviceInfo;
352ee0d6f44d7274bb1846cc6ff7a60451539a2b51Jinsuk Kimimport android.hardware.hdmi.HdmiPortInfo;
36b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jangimport android.hardware.hdmi.HdmiRecordSources;
3712e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jangimport android.hardware.hdmi.HdmiTimerRecordSources;
3860cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jangimport android.hardware.hdmi.IHdmiControlCallback;
39b69aafbfaddd8a6ac84b366b5db640cdd7e95354Jungshik Jangimport android.media.AudioManager;
40a858d221ff86c497e745222ea15bab141e337636Jungshik Jangimport android.media.AudioSystem;
417fa3a66470d2133796defd14a0600578758882acJinsuk Kimimport android.media.tv.TvInputInfo;
427fa3a66470d2133796defd14a0600578758882acJinsuk Kimimport android.media.tv.TvInputManager.TvInputCallback;
43a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kimimport android.os.RemoteException;
447ecfbaed6e902aea151bc1919cf7771bbd868fc4Jinsuk Kimimport android.provider.Settings.Global;
454fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kimimport android.util.ArraySet;
46092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jangimport android.util.Slog;
4779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jangimport android.util.SparseArray;
485bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kimimport android.util.SparseBooleanArray;
49092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang
5079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jangimport com.android.internal.annotations.GuardedBy;
51959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heoimport com.android.internal.util.IndentingPrintWriter;
5260cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jangimport com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
53a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jangimport com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
54e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jangimport com.android.server.hdmi.HdmiControlService.SendMessageCallback;
558f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jangimport java.io.UnsupportedEncodingException;
5679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jangimport java.util.ArrayList;
57b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jangimport java.util.Arrays;
584fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kimimport java.util.Collection;
590a3316bcfdac9f5f40d1349d97d10329c70f7e30Jinsuk Kimimport java.util.Collections;
604fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kimimport java.util.Iterator;
610a3316bcfdac9f5f40d1349d97d10329c70f7e30Jinsuk Kimimport java.util.List;
626e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kimimport java.util.HashMap;
632918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim
642918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim/**
652918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim * Represent a logical device of type TV residing in Android system.
662918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim */
672918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kimfinal class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
68092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang    private static final String TAG = "HdmiCecLocalDeviceTv";
692918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim
7012e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang    // Whether ARC is available or not. "true" means that ARC is established between TV and
71a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    // AVR as audio receiver.
72a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    @ServiceThreadOnly
73a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    private boolean mArcEstablished = false;
74a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang
755bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim    // Stores whether ARC feature is enabled per port. True by default for all the ARC-enabled ports.
765bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim    private final SparseBooleanArray mArcFeatureEnabled = new SparseBooleanArray();
7779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
78377dcbd53af4529c352d453424539b069909fce4Jungshik Jang    // Whether System audio mode is activated or not.
79377dcbd53af4529c352d453424539b069909fce4Jungshik Jang    // This becomes true only when all system audio sequences are finished.
80fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    @GuardedBy("mLock")
81377dcbd53af4529c352d453424539b069909fce4Jungshik Jang    private boolean mSystemAudioActivated = false;
8279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
838333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    // The previous port id (input) before switching to the new one. This is remembered in order to
848333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    // be able to switch to it upon receiving <Inactive Source> from currently active source.
858333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    // This remains valid only when the active source was switched via one touch play operation
868333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    // (either by TV or source device). Manual port switching invalidates this value to
87c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim    // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything.
888333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    @GuardedBy("mLock")
898333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    private int mPrevPortId;
908333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
918fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    @GuardedBy("mLock")
92c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim    private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME;
938fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang
948fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    @GuardedBy("mLock")
958fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    private boolean mSystemAudioMute = false;
968fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang
97fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    // Copy of mDeviceInfos to guarantee thread-safety.
98fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    @GuardedBy("mLock")
9961f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang    private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
1009c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim    // All external cec input(source) devices. Does not include system audio device.
101fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    @GuardedBy("mLock")
10261f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang    private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList();
103fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang
10479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    // Map-like container of all cec devices including local ones.
1058960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim    // device id is used as key of container.
106fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    // This is not thread-safe. For external purpose use mSafeDeviceInfos.
10761f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang    private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
10879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
109160a6e5b99de15ce755e2e5521dce32d81ab180aJinsuk Kim    // If true, TV going to standby mode puts other devices also to standby.
110160a6e5b99de15ce755e2e5521dce32d81ab180aJinsuk Kim    private boolean mAutoDeviceOff;
111160a6e5b99de15ce755e2e5521dce32d81ab180aJinsuk Kim
112544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim    // If true, TV wakes itself up when receiving <Text/Image View On>.
113544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim    private boolean mAutoWakeup;
114544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim
115bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim    // List of the logical address of local CEC devices. Unmodifiable, thread-safe.
116bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim    private List<Integer> mLocalDeviceAddresses;
117bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim
11825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo    private final HdmiCecStandbyModeHandler mStandbyHandler;
11925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
120d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim    // If true, do not do routing control/send active source for internal source.
121d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim    // Set to true when the device was woken up by <Text/Image View On>.
122d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim    private boolean mSkipRoutingControl;
123d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim
1244fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
1254fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    // other CEC devices since they might not have logical address.
1264fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
1274fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim
1287fa3a66470d2133796defd14a0600578758882acJinsuk Kim    // Message buffer used to buffer selected messages to process later. <Active Source>
1297fa3a66470d2133796defd14a0600578758882acJinsuk Kim    // from a source device, for instance, needs to be buffered if the device is not
1307fa3a66470d2133796defd14a0600578758882acJinsuk Kim    // discovered yet. The buffered commands are taken out and when they are ready to
1317fa3a66470d2133796defd14a0600578758882acJinsuk Kim    // handle.
1327fa3a66470d2133796defd14a0600578758882acJinsuk Kim    private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
1337fa3a66470d2133796defd14a0600578758882acJinsuk Kim
1347fa3a66470d2133796defd14a0600578758882acJinsuk Kim    // Defines the callback invoked when TV input framework is updated with input status.
1357fa3a66470d2133796defd14a0600578758882acJinsuk Kim    // We are interested in the notification for HDMI input addition event, in order to
1367fa3a66470d2133796defd14a0600578758882acJinsuk Kim    // process any CEC commands that arrived before the input is added.
1377fa3a66470d2133796defd14a0600578758882acJinsuk Kim    private final TvInputCallback mTvInputCallback = new TvInputCallback() {
1387fa3a66470d2133796defd14a0600578758882acJinsuk Kim        @Override
1397fa3a66470d2133796defd14a0600578758882acJinsuk Kim        public void onInputAdded(String inputId) {
1407fa3a66470d2133796defd14a0600578758882acJinsuk Kim            TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
1417fa3a66470d2133796defd14a0600578758882acJinsuk Kim            HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
1426e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim            if (info == null) return;
1436e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim            addTvInput(inputId, info.getId());
1446e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim            if (info.isCecDevice()) {
1456e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim                processDelayedActiveSource(info.getLogicalAddress());
1467fa3a66470d2133796defd14a0600578758882acJinsuk Kim            }
1477fa3a66470d2133796defd14a0600578758882acJinsuk Kim        }
1486e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim
1496e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        @Override
1506e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        public void onInputRemoved(String inputId) {
1516e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim            removeTvInput(inputId);
1526e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        }
1537fa3a66470d2133796defd14a0600578758882acJinsuk Kim    };
1547fa3a66470d2133796defd14a0600578758882acJinsuk Kim
1556e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    // Keeps the mapping (TV input ID, HDMI device ID) to keep track of the TV inputs ready to
1566e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    // accept input switching request from HDMI devices. Requests for which the corresponding
1576e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    // input ID is not yet registered by TV input framework need to be buffered for delayed
1586e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    // processing.
1596e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    private final HashMap<String, Integer> mTvInputs = new HashMap<>();
1606e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim
1616e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    @ServiceThreadOnly
1626e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    private void addTvInput(String inputId, int deviceId) {
1636e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        assertRunOnServiceThread();
1646e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        mTvInputs.put(inputId, deviceId);
1656e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    }
1666e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim
1676e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    @ServiceThreadOnly
1686e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    private void removeTvInput(String inputId) {
1696e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        assertRunOnServiceThread();
1706e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        mTvInputs.remove(inputId);
1716e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    }
1726e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim
1736e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    @Override
1746e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    @ServiceThreadOnly
1756e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    protected boolean isInputReady(int deviceId) {
1766e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        assertRunOnServiceThread();
1776e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        return mTvInputs.containsValue(deviceId);
1786e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    }
1796e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim
1803ee65720e91c7f92ad5a034d7052122a606aa8d5Jungshik Jang    HdmiCecLocalDeviceTv(HdmiControlService service) {
18161f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang        super(service, HdmiDeviceInfo.DEVICE_TV);
182c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim        mPrevPortId = Constants.INVALID_PORT_ID;
183544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim        mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
184544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim                true);
185544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim        mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true);
18625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo        mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
1878b308d93c8fdcc7304b33d9b445ae3807eae97c8Jungshik Jang    }
1882918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim
1898b308d93c8fdcc7304b33d9b445ae3807eae97c8Jungshik Jang    @Override
190a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
191fc44e4e03c5f6486efb7457965dcf7eaf36bc971Yuncheol Heo    protected void onAddressAllocated(int logicalAddress, int reason) {
19279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
1935bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim        List<HdmiPortInfo> ports = mService.getPortInfo();
1945bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim        for (HdmiPortInfo port : ports) {
1955bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim            mArcFeatureEnabled.put(port.getId(), port.isArcSupported());
1965bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim        }
1977fa3a66470d2133796defd14a0600578758882acJinsuk Kim        mService.registerTvInputCallback(mTvInputCallback);
1983ee65720e91c7f92ad5a034d7052122a606aa8d5Jungshik Jang        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
1993ee65720e91c7f92ad5a034d7052122a606aa8d5Jungshik Jang                mAddress, mService.getPhysicalAddress(), mDeviceType));
2003ee65720e91c7f92ad5a034d7052122a606aa8d5Jungshik Jang        mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
2013ee65720e91c7f92ad5a034d7052122a606aa8d5Jungshik Jang                mAddress, mService.getVendorId()));
2024fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        mCecSwitches.add(mService.getPhysicalAddress());  // TV is a CEC switch too.
2036e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        mTvInputs.clear();
204d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim        mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
205fc44e4e03c5f6486efb7457965dcf7eaf36bc971Yuncheol Heo        launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
206fc44e4e03c5f6486efb7457965dcf7eaf36bc971Yuncheol Heo                reason != HdmiControlService.INITIATED_BY_BOOT_UP);
207bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        mLocalDeviceAddresses = initLocalDeviceAddresses();
20860cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang        launchDeviceDiscovery();
2096f34f5ab8ab1b1db7887e5405d8b0031e105ab05Jungshik Jang    }
2106f34f5ab8ab1b1db7887e5405d8b0031e105ab05Jungshik Jang
211bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim
212bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim    @ServiceThreadOnly
213bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim    private List<Integer> initLocalDeviceAddresses() {
214bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        assertRunOnServiceThread();
215bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        List<Integer> addresses = new ArrayList<>();
216bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
217bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim            addresses.add(device.getDeviceInfo().getLogicalAddress());
218bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        }
219bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        return Collections.unmodifiableList(addresses);
220bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim    }
221bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim
222af2acf0447aff34450cde2bcfb35dff9cf631729Jinsuk Kim    @Override
223af2acf0447aff34450cde2bcfb35dff9cf631729Jinsuk Kim    protected int getPreferredAddress() {
224d47abefc8269dae7fdfa2bb102bcb89cbea7c7b0Jinsuk Kim        return Constants.ADDR_TV;
225af2acf0447aff34450cde2bcfb35dff9cf631729Jinsuk Kim    }
226af2acf0447aff34450cde2bcfb35dff9cf631729Jinsuk Kim
227af2acf0447aff34450cde2bcfb35dff9cf631729Jinsuk Kim    @Override
228af2acf0447aff34450cde2bcfb35dff9cf631729Jinsuk Kim    protected void setPreferredAddress(int addr) {
229d47abefc8269dae7fdfa2bb102bcb89cbea7c7b0Jinsuk Kim        Slog.w(TAG, "Preferred addres will not be stored for TV");
230af2acf0447aff34450cde2bcfb35dff9cf631729Jinsuk Kim    }
231af2acf0447aff34450cde2bcfb35dff9cf631729Jinsuk Kim
23225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo    @Override
23325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo    @ServiceThreadOnly
23425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo    boolean dispatchMessage(HdmiCecMessage message) {
23525c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo        assertRunOnServiceThread();
23625c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo        if (mService.isPowerStandby() && mStandbyHandler.handleCommand(message)) {
23725c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo            return true;
23825c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo        }
23925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo        return super.onMessage(message);
24025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo    }
24125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
242a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim    /**
243a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim     * Performs the action 'device select', or 'one touch play' initiated by TV.
244a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim     *
2458960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim     * @param id id of HDMI device to select
246a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim     * @param callback callback object to report the result with
247a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim     */
248a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
2498960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim    void deviceSelect(int id, IHdmiControlCallback callback) {
25079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
2518960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        HdmiDeviceInfo targetDevice = mDeviceInfos.get(id);
2528960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        if (targetDevice == null) {
2538960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
2548960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            return;
2558960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        }
2568960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        int targetAddress = targetDevice.getLogicalAddress();
25758500f43ecbed3f92d7c077fb6ce396252cd00eaJinsuk Kim        ActiveSource active = getActiveSource();
2586b2a6177aad44466794a9262a4f2f2c209a3f2e5Yu.Ishihara        if (targetDevice.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON
2596b2a6177aad44466794a9262a4f2f2c209a3f2e5Yu.Ishihara                && active.isValid()
2606b2a6177aad44466794a9262a4f2f2c209a3f2e5Yu.Ishihara                && targetAddress == active.logicalAddress) {
26158500f43ecbed3f92d7c077fb6ce396252cd00eaJinsuk Kim            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
26258500f43ecbed3f92d7c077fb6ce396252cd00eaJinsuk Kim            return;
26358500f43ecbed3f92d7c077fb6ce396252cd00eaJinsuk Kim        }
264c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim        if (targetAddress == Constants.ADDR_INTERNAL) {
2655ad57168da6456e8e4935aaa8512a7f77b74b0a1Jinsuk Kim            handleSelectInternalSource();
2665ad57168da6456e8e4935aaa8512a7f77b74b0a1Jinsuk Kim            // Switching to internal source is always successful even when CEC control is disabled.
26772b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim            setActiveSource(targetAddress, mService.getPhysicalAddress());
2687e74206693f4ee93afb902d5b3446e2384f2a13dJinsuk Kim            setActivePath(mService.getPhysicalAddress());
2695ad57168da6456e8e4935aaa8512a7f77b74b0a1Jinsuk Kim            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
2708333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            return;
2718333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        }
27209ffc846af78f949d2847003db9f793bfb5eefaaJinsuk Kim        if (!mService.isControlEnabled()) {
2738960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            setActiveSource(targetDevice);
27409ffc846af78f949d2847003db9f793bfb5eefaaJinsuk Kim            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
27509ffc846af78f949d2847003db9f793bfb5eefaaJinsuk Kim            return;
27609ffc846af78f949d2847003db9f793bfb5eefaaJinsuk Kim        }
27779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        removeAction(DeviceSelectAction.class);
27879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
279a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim    }
280a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim
2818333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    @ServiceThreadOnly
2825ad57168da6456e8e4935aaa8512a7f77b74b0a1Jinsuk Kim    private void handleSelectInternalSource() {
2838333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        assertRunOnServiceThread();
2848333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        // Seq #18
28572b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) {
2868333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            updateActiveSource(mAddress, mService.getPhysicalAddress());
287d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim            if (mSkipRoutingControl) {
288d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim                mSkipRoutingControl = false;
289d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim                return;
290d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim            }
2918333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
2928333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim                    mAddress, mService.getPhysicalAddress());
2938333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            mService.sendCecCommand(activeSource);
2948333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        }
2958333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    }
2968333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
2978333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    @ServiceThreadOnly
29872b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim    void updateActiveSource(int logicalAddress, int physicalAddress) {
29972b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        assertRunOnServiceThread();
30072b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress));
30172b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim    }
30272b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim
30372b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim    @ServiceThreadOnly
30472b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim    void updateActiveSource(ActiveSource newActive) {
3058333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        assertRunOnServiceThread();
3068333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        // Seq #14
30772b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        if (mActiveSource.equals(newActive)) {
3088333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            return;
3098333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        }
31072b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        setActiveSource(newActive);
31172b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        int logicalAddress = newActive.logicalAddress;
3128960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) {
31372b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim            if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
3148333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim                setPrevPortId(getActivePortId());
3158333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            }
3168333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            // TODO: Show the OSD banner related to the new active source device.
3178333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        } else {
3188333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            // TODO: If displayed, remove the OSD banner related to the previous
3198333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            //       active source device.
3208333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        }
3218333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    }
3228333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
3232b152015ff94f20b9ec3ef284fb83105f8b3c831Jinsuk Kim    int getPortId(int physicalAddress) {
3242b152015ff94f20b9ec3ef284fb83105f8b3c831Jinsuk Kim        return mService.pathToPortId(physicalAddress);
3252b152015ff94f20b9ec3ef284fb83105f8b3c831Jinsuk Kim    }
3262b152015ff94f20b9ec3ef284fb83105f8b3c831Jinsuk Kim
327a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim    /**
3288333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim     * Returns the previous port id kept to handle input switching on <Inactive Source>.
3298333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim     */
3308333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    int getPrevPortId() {
3318333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        synchronized (mLock) {
3328333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            return mPrevPortId;
3338333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        }
3348333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    }
3358333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
3368333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    /**
3378333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim     * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
3388333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim     * taken for <Inactive Source>.
339a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim     */
3408333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    void setPrevPortId(int portId) {
3418333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        synchronized (mLock) {
3428333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            mPrevPortId = portId;
3438333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        }
3448333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    }
3458333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
346a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
34772b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim    void updateActiveInput(int path, boolean notifyInputChange) {
348a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim        assertRunOnServiceThread();
3498333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        // Seq #15
35072b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        setActivePath(path);
35172b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        // TODO: Handle PAP/PIP case.
35272b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        // Show OSD port change banner
35372b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        if (notifyInputChange) {
35472b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim            ActiveSource activeSource = getActiveSource();
3558960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress);
35672b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim            if (info == null) {
3576ad7cbde22fe9752082ce01c9b9be213b07afefcJinsuk Kim                info = mService.getDeviceInfoByPort(getActivePortId());
3586ad7cbde22fe9752082ce01c9b9be213b07afefcJinsuk Kim                if (info == null) {
3596ad7cbde22fe9752082ce01c9b9be213b07afefcJinsuk Kim                    // No CEC/MHL device is present at the port. Attempt to switch to
3606ad7cbde22fe9752082ce01c9b9be213b07afefcJinsuk Kim                    // the hardware port itself for non-CEC devices that may be connected.
3616ad7cbde22fe9752082ce01c9b9be213b07afefcJinsuk Kim                    info = new HdmiDeviceInfo(path, getActivePortId());
3626ad7cbde22fe9752082ce01c9b9be213b07afefcJinsuk Kim                }
36372b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim            }
36472b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim            mService.invokeInputChangeListener(info);
36572b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        }
3668333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    }
3678333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
3688333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    @ServiceThreadOnly
3698333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
3708333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        assertRunOnServiceThread();
3718333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        // Seq #20
37209ffc846af78f949d2847003db9f793bfb5eefaaJinsuk Kim        if (!mService.isValidPortId(portId)) {
373c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
374a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim            return;
375a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim        }
37672b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        if (portId == getActivePortId()) {
37772b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
37872b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim            return;
37972b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        }
38043c23e273e1b78caf26899eca5a4f51df9d52400Jinsuk Kim        mActiveSource.invalidate();
38109ffc846af78f949d2847003db9f793bfb5eefaaJinsuk Kim        if (!mService.isControlEnabled()) {
38209ffc846af78f949d2847003db9f793bfb5eefaaJinsuk Kim            setActivePortId(portId);
38309ffc846af78f949d2847003db9f793bfb5eefaaJinsuk Kim            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
38409ffc846af78f949d2847003db9f793bfb5eefaaJinsuk Kim            return;
38509ffc846af78f949d2847003db9f793bfb5eefaaJinsuk Kim        }
3861f8d1c576f06ed63c21d175fb0c86db596f59353Jinsuk Kim        int oldPath = getActivePortId() != Constants.INVALID_PORT_ID
3871f8d1c576f06ed63c21d175fb0c86db596f59353Jinsuk Kim                ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress();
388d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim        setActivePath(oldPath);
389d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim        if (mSkipRoutingControl) {
390d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim            mSkipRoutingControl = false;
391d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim            return;
392d530719b169c8a10dccaefc413261e1b127cee63Jinsuk Kim        }
393a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim        int newPath = mService.portIdToPath(portId);
394546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim        startRoutingControl(oldPath, newPath, true, callback);
395546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim    }
396546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim
397546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim    @ServiceThreadOnly
398546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim    void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus,
399546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim            IHdmiControlCallback callback) {
400546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim        assertRunOnServiceThread();
401546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim        if (oldPath == newPath) {
402546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim            return;
403546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim        }
404a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim        HdmiCecMessage routingChange =
405a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim                HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
406a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim        mService.sendCecCommand(routingChange);
40772b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        removeAction(RoutingControlAction.class);
408546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim        addAndStartAction(
409546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim                new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback));
410a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim    }
411a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim
412f67113f7abd536cc3eb888344bf925762aa5278eJungshik Jang    @ServiceThreadOnly
413cae6627702a3bae3121f0ebbb9c8069e77f81cc7Jinsuk Kim    int getPowerStatus() {
414f67113f7abd536cc3eb888344bf925762aa5278eJungshik Jang        assertRunOnServiceThread();
415cae6627702a3bae3121f0ebbb9c8069e77f81cc7Jinsuk Kim        return mService.getPowerStatus();
416cae6627702a3bae3121f0ebbb9c8069e77f81cc7Jinsuk Kim    }
417cae6627702a3bae3121f0ebbb9c8069e77f81cc7Jinsuk Kim
418a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim    /**
419a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim     * Sends key to a target CEC device.
420a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim     *
421fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang     * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
422c068bb5a0468bf605b0398e6f0ea5721917de4eeJinsuk Kim     * @param isPressed true if this is key press event
423a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim     */
424c068bb5a0468bf605b0398e6f0ea5721917de4eeJinsuk Kim    @Override
425a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
426c068bb5a0468bf605b0398e6f0ea5721917de4eeJinsuk Kim    protected void sendKeyEvent(int keyCode, boolean isPressed) {
427a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim        assertRunOnServiceThread();
428b64c2ba05bf64bb8015444fdcd706fa3238cb96cDongil Seo        if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) {
429b64c2ba05bf64bb8015444fdcd706fa3238cb96cDongil Seo            Slog.w(TAG, "Unsupported key: " + keyCode);
430b64c2ba05bf64bb8015444fdcd706fa3238cb96cDongil Seo            return;
431b64c2ba05bf64bb8015444fdcd706fa3238cb96cDongil Seo        }
432a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim        List<SendKeyAction> action = getActions(SendKeyAction.class);
433454fab52195f86d08d0b7626ed170af113e44695Jinsuk Kim        int logicalAddress = findKeyReceiverAddress();
434454fab52195f86d08d0b7626ed170af113e44695Jinsuk Kim        if (logicalAddress == mAddress) {
435454fab52195f86d08d0b7626ed170af113e44695Jinsuk Kim            Slog.w(TAG, "Discard key event to itself :" + keyCode + " pressed:" + isPressed);
436454fab52195f86d08d0b7626ed170af113e44695Jinsuk Kim            return;
437454fab52195f86d08d0b7626ed170af113e44695Jinsuk Kim        }
438a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim        if (!action.isEmpty()) {
439a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim            action.get(0).processKeyEvent(keyCode, isPressed);
440a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim        } else {
4417543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim            if (isPressed) {
4427543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim                if (logicalAddress != Constants.ADDR_INVALID) {
4437543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim                    addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
4447543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim                    return;
4457543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim                }
446a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim            }
4477543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim            Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed);
448a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim        }
449a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim    }
450a062a9339add79a84862a34e363e3e454a6ec435Jinsuk Kim
4517543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim    private int findKeyReceiverAddress() {
4527543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim        if (getActiveSource().isValid()) {
4537543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim            return getActiveSource().logicalAddress;
4547543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim        }
4557543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim        HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath());
4567543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim        if (info != null) {
4577543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim            return info.getLogicalAddress();
4587543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim        }
4597543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim        return Constants.ADDR_INVALID;
4607543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim    }
4617543497f8a8a8ba15edd622061f5d30dfbf6655aJinsuk Kim
462a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim    private static void invokeCallback(IHdmiControlCallback callback, int result) {
4634893c7efde52411ad051ef5c20251439f4098eacJinsuk Kim        if (callback == null) {
4644893c7efde52411ad051ef5c20251439f4098eacJinsuk Kim            return;
4654893c7efde52411ad051ef5c20251439f4098eacJinsuk Kim        }
466a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim        try {
467a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim            callback.onComplete(result);
468a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim        } catch (RemoteException e) {
469a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim            Slog.e(TAG, "Invoking callback failed:" + e);
470a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim        }
471a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim    }
472a6ce7708d6124224399241503fadcafe0c4684d4Jinsuk Kim
473092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang    @Override
474a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
4758333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    protected boolean handleActiveSource(HdmiCecMessage message) {
4768333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        assertRunOnServiceThread();
47772b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        int logicalAddress = message.getSource();
47872b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
479bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
480bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        if (info == null) {
4817fa3a66470d2133796defd14a0600578758882acJinsuk Kim            if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
482cb8661c08f4a7b00eaa2ede06a30c32dd3cbc53bJinsuk Kim                HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
4837fa3a66470d2133796defd14a0600578758882acJinsuk Kim                mDelayedMessageBuffer.add(message);
4847fa3a66470d2133796defd14a0600578758882acJinsuk Kim            }
4856e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        } else if (!isInputReady(info.getId())) {
4866e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim            HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
4876e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim            mDelayedMessageBuffer.add(message);
48892b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        } else {
4897cc51c631d6e7e5680ce661089524b7335d88756Jinsuk Kim            updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
49072b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim            ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
491bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim            ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
49292b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        }
4938333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        return true;
4948333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    }
4958333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
4968333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    @Override
4978333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    @ServiceThreadOnly
4988333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    protected boolean handleInactiveSource(HdmiCecMessage message) {
4998333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        assertRunOnServiceThread();
5008333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        // Seq #10
5018333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
5028333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        // Ignore <Inactive Source> from non-active source device.
50372b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        if (getActiveSource().logicalAddress != message.getSource()) {
5048333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            return true;
5058333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        }
5064d43d93743222311c6377d4904c19ccb93699d3bJinsuk Kim        if (isProhibitMode()) {
5078333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            return true;
5088333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        }
5098333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        int portId = getPrevPortId();
510c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim        if (portId != Constants.INVALID_PORT_ID) {
5118333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            // TODO: Do this only if TV is not showing multiview like PIP/PAP.
5128333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
5138960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource());
5148333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            if (inactiveSource == null) {
5158333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim                return true;
5168333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            }
5178333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
5188333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim                return true;
5198333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            }
5208333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            // TODO: Switch the TV freeze mode off
5218333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
5228333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            doManualPortSwitching(portId, null);
523c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim            setPrevPortId(Constants.INVALID_PORT_ID);
524cb8661c08f4a7b00eaa2ede06a30c32dd3cbc53bJinsuk Kim        } else {
525cb8661c08f4a7b00eaa2ede06a30c32dd3cbc53bJinsuk Kim            // No HDMI port to switch to was found. Notify the input change listers to
526cb8661c08f4a7b00eaa2ede06a30c32dd3cbc53bJinsuk Kim            // switch to the lastly shown internal input.
527cb8661c08f4a7b00eaa2ede06a30c32dd3cbc53bJinsuk Kim            mActiveSource.invalidate();
528cb8661c08f4a7b00eaa2ede06a30c32dd3cbc53bJinsuk Kim            setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
529cb8661c08f4a7b00eaa2ede06a30c32dd3cbc53bJinsuk Kim            mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
5308333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        }
5318333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        return true;
5328333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    }
5338333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
5348333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    @Override
5358333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    @ServiceThreadOnly
5368333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
5378333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        assertRunOnServiceThread();
5388333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        // Seq #19
53972b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        if (mAddress == getActiveSource().logicalAddress) {
5408333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim            mService.sendCecCommand(
54192b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim                    HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
5428333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        }
5438333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim        return true;
5448333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    }
5458333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim
5468333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    @Override
5478333571bd5e0a08773a1679964f8d96227af3356Jinsuk Kim    @ServiceThreadOnly
548092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
54979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
550f67113f7abd536cc3eb888344bf925762aa5278eJungshik Jang        if (!broadcastMenuLanguage(mService.getLanguage())) {
551092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang            Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
552092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang        }
553092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang        return true;
554092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang    }
555092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang
5561ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo    @ServiceThreadOnly
5571ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo    boolean broadcastMenuLanguage(String language) {
5581ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo        assertRunOnServiceThread();
5591ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
5601ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo                mAddress, language);
5611ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo        if (command != null) {
5621ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo            mService.sendCecCommand(command);
5631ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo            return true;
5641ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo        }
5651ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo        return false;
5661ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo    }
5671ca0a43251a31bb1b4253dc404316cc4b840f497Terry Heo
56860cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang    @Override
569a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
57060cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
57179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
5724fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        int path = HdmiUtils.twoBytesToInt(message.getParams());
5734fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        int address = message.getSource();
574bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim        int type = message.getParams()[2];
5754fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim
576bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim        if (updateCecSwitchInfo(address, type, path)) return true;
5774fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim
578092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang        // Ignore if [Device Discovery Action] is going on.
57979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        if (hasAction(DeviceDiscoveryAction.class)) {
5804fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
581092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang            return true;
582092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang        }
583092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang
5844b4b940c69d22304eb0734ed2432fc70ebadd462Jinsuk Kim        if (!isInDeviceList(address, path)) {
5858f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang            handleNewDeviceAtTheTailOfActivePath(path);
58692b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        }
5877cd4a589af7cc6e6880799e86ef6febca5add46dJinsuk Kim
5887cd4a589af7cc6e6880799e86ef6febca5add46dJinsuk Kim        // Add the device ahead with default information to handle <Active Source>
5897cd4a589af7cc6e6880799e86ef6febca5add46dJinsuk Kim        // promptly, rather than waiting till the new device action is finished.
5907cd4a589af7cc6e6880799e86ef6febca5add46dJinsuk Kim        HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type,
5917cd4a589af7cc6e6880799e86ef6febca5add46dJinsuk Kim                Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address));
5927cd4a589af7cc6e6880799e86ef6febca5add46dJinsuk Kim        addCecDevice(deviceInfo);
593bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        startNewDeviceAction(ActiveSource.of(address, path), type);
59492b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        return true;
59592b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    }
59692b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim
5974480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang    @Override
5984480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang    protected boolean handleReportPowerStatus(HdmiCecMessage command) {
5994480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        int newStatus = command.getParams()[0] & 0xFF;
6004480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        updateDevicePowerStatus(command.getSource(), newStatus);
6014480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        return true;
6024480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang    }
6034480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang
6044480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang    @Override
6054480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang    protected boolean handleTimerStatus(HdmiCecMessage message) {
6064480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        // Do nothing.
6074480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        return true;
6084480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang    }
6094480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang
6104480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang    @Override
6114480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang    protected boolean handleRecordStatus(HdmiCecMessage message) {
6124480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        // Do nothing.
6134480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        return true;
6144480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang    }
6154480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang
616bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim    boolean updateCecSwitchInfo(int address, int type, int path) {
617bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim        if (address == Constants.ADDR_UNREGISTERED
618bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim                && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
619bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim            mCecSwitches.add(path);
620bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim            updateSafeDeviceInfoList();
621bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim            return true;  // Pure switch does not need further processing. Return here.
622bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim        }
623bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim        if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
624bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim            mCecSwitches.add(path);
625bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim        }
626bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim        return false;
627bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim    }
628bcfa0677d4b015457b73b1147c96e4ad2946b2beJinsuk Kim
629bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim    void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
63097affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang        for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
63197affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang            // If there is new device action which has the same logical address and path
63297affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang            // ignore new request.
63397affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang            // NewDeviceAction is created whenever it receives <Report Physical Address>.
63497affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang            // And there is a chance starting NewDeviceAction for the same source.
63597affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang            // Usually, new device sends <Report Physical Address> when it's plugged
63697affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang            // in. However, TV can detect a new device from HotPlugDetectionAction,
63797affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang            // which sends <Give Physical Address> to the source for newly detected
63897affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang            // device.
63972b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim            if (action.isActionOf(activeSource)) {
64097affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang                return;
64197affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang            }
64297affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang        }
64397affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang
64472b7d738d5b9254594726304cdb1777b54d95631Jinsuk Kim        addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
645bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim                activeSource.physicalAddress, deviceType));
64697affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang    }
64797affee67b6d88da40af41b36f02ecb2b823daffJungshik Jang
6487fa3a66470d2133796defd14a0600578758882acJinsuk Kim    private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
64992b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        // Seq #22
65092b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        if (isTailOfActivePath(path, getActivePath())) {
65192b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim            int newPath = mService.portIdToPath(getActivePortId());
6527c3a95633d307c4be30c9dbbf1071063aa7a3c64Jinsuk Kim            setActivePath(newPath);
653546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim            startRoutingControl(getActivePath(), newPath, false, null);
6547fa3a66470d2133796defd14a0600578758882acJinsuk Kim            return true;
65592b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        }
6567fa3a66470d2133796defd14a0600578758882acJinsuk Kim        return false;
65792b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    }
658092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang
65992b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    /**
66092b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim     * Whether the given path is located in the tail of current active path.
66192b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim     *
66292b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim     * @param path to be tested
66392b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim     * @param activePath current active path
66492b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim     * @return true if the given path is located in the tail of current active path; otherwise,
66592b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim     *         false
66692b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim     */
66792b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    static boolean isTailOfActivePath(int path, int activePath) {
66892b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        // If active routing path is internal source, return false.
66992b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        if (activePath == 0) {
67092b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim            return false;
67192b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        }
67292b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        for (int i = 12; i >= 0; i -= 4) {
67392b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim            int curActivePath = (activePath >> i) & 0xF;
67492b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim            if (curActivePath == 0) {
67592b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim                return true;
67692b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim            } else {
67792b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim                int curPath = (path >> i) & 0xF;
67892b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim                if (curPath != curActivePath) {
67992b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim                    return false;
68092b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim                }
68192b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim            }
68292b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        }
68392b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        return false;
68492b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    }
68592b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim
68692b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    @Override
68792b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    @ServiceThreadOnly
68892b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    protected boolean handleRoutingChange(HdmiCecMessage message) {
68992b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        assertRunOnServiceThread();
69092b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        // Seq #21
69192b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        byte[] params = message.getParams();
69292b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        int currentPath = HdmiUtils.twoBytesToInt(params);
69392b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
69443c23e273e1b78caf26899eca5a4f51df9d52400Jinsuk Kim            mActiveSource.invalidate();
69592b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim            removeAction(RoutingControlAction.class);
69643c23e273e1b78caf26899eca5a4f51df9d52400Jinsuk Kim            int newPath = HdmiUtils.twoBytesToInt(params, 2);
69704fd28046acc2ac74339ed94cec76a0bfda846f7Jinsuk Kim            addAndStartAction(new RoutingControlAction(this, newPath, true, null));
69892b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        }
699092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang        return true;
700092b445ef898e3c1e5b2918b554480940f0f5a28Jungshik Jang    }
7010a3316bcfdac9f5f40d1349d97d10329c70f7e30Jinsuk Kim
7020a3316bcfdac9f5f40d1349d97d10329c70f7e30Jinsuk Kim    @Override
703a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
7048fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
7058fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        assertRunOnServiceThread();
7068fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang
7078fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        byte params[] = message.getParams();
7088fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        int mute = params[0] & 0x80;
7098fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        int volume = params[0] & 0x7F;
7108fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        setAudioStatus(mute == 0x80, volume);
7118fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        return true;
7128fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    }
7138fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang
71438db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    @Override
71538db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    @ServiceThreadOnly
71638db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    protected boolean handleTextViewOn(HdmiCecMessage message) {
71738db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        assertRunOnServiceThread();
718544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim        if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
71938db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo            mService.wakeUp();
72038db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        }
72138db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        return true;
72238db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    }
72338db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo
72438db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    @Override
72538db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    @ServiceThreadOnly
72638db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    protected boolean handleImageViewOn(HdmiCecMessage message) {
72738db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        assertRunOnServiceThread();
72838db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        // Currently, it's the same as <Text View On>.
72938db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        return handleTextViewOn(message);
73038db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    }
73138db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo
7328f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang    @Override
7338f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang    @ServiceThreadOnly
7348f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang    protected boolean handleSetOsdName(HdmiCecMessage message) {
7358f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        int source = message.getSource();
7368960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
7378f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        // If the device is not in device list, ignore it.
7388f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        if (deviceInfo == null) {
7398f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang            Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
7408f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang            return true;
7418f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        }
7428f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        String osdName = null;
7438f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        try {
7448f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang            osdName = new String(message.getParams(), "US-ASCII");
7458f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        } catch (UnsupportedEncodingException e) {
7468f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang            Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
7478f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang            return true;
7488f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        }
7498f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang
7508f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        if (deviceInfo.getDisplayName().equals(osdName)) {
7518f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang            Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
7528f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang            return true;
7538f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        }
7548f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang
75561f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang        addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
7562b152015ff94f20b9ec3ef284fb83105f8b3c831Jinsuk Kim                deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
7572b152015ff94f20b9ec3ef284fb83105f8b3c831Jinsuk Kim                deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
7588f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang        return true;
7598f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang    }
7608f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang
761a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
76260cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang    private void launchDeviceDiscovery() {
76379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
76479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        clearDeviceInfoList();
76579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
76660cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang                new DeviceDiscoveryCallback() {
76760cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang                    @Override
76861f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang                    public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
76961f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang                        for (HdmiDeviceInfo info : deviceInfos) {
77079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang                            addCecDevice(info);
77160cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang                        }
77260cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang
77360cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang                        // Since we removed all devices when it's start and
77460cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang                        // device discovery action does not poll local devices,
77560cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang                        // we should put device info of local device manually here
77660cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
77779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang                            addCecDevice(device.getDeviceInfo());
77860cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang                        }
77960cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang
78079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang                        addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
781410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang                        addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
782187d01765b935d07936f74343b4f4af590c239a1Jungshik Jang
783187d01765b935d07936f74343b4f4af590c239a1Jungshik Jang                        // If there is AVR, initiate System Audio Auto initiation action,
784187d01765b935d07936f74343b4f4af590c239a1Jungshik Jang                        // which turns on and off system audio according to last system
785187d01765b935d07936f74343b4f4af590c239a1Jungshik Jang                        // audio setting.
786339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang                        HdmiDeviceInfo avr = getAvrDeviceInfo();
787339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang                        if (avr != null) {
788339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang                            onNewAvrAdded(avr);
789951e3e42f5943d90b74d9feb9fbc224afa59ed31Wally Yau                        } else {
790951e3e42f5943d90b74d9feb9fbc224afa59ed31Wally Yau                            setSystemAudioMode(false, true);
791187d01765b935d07936f74343b4f4af590c239a1Jungshik Jang                        }
79260cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang                    }
79360cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang                });
79479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        addAndStartAction(action);
79579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
79679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
797339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang    @ServiceThreadOnly
798339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang    void onNewAvrAdded(HdmiDeviceInfo avr) {
799339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        assertRunOnServiceThread();
800ad1e3d7df42376cd2a257b6c3b2fed540658a6e3Jinsuk Kim        if (getSystemAudioModeSetting() && !isSystemAudioActivated()) {
8017fa3a66470d2133796defd14a0600578758882acJinsuk Kim            addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
8027fa3a66470d2133796defd14a0600578758882acJinsuk Kim        }
8035bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim        if (isArcFeatureEnabled(avr.getPortId())
8045bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim                && !hasAction(SetArcTransmissionStateAction.class)) {
805339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            startArcAction(true);
806339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        }
807339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang    }
808339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang
80979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    // Clear all device info.
810a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
81179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    private void clearDeviceInfoList() {
81279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
81361f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang        for (HdmiDeviceInfo info : mSafeExternalInputs) {
81461daf6b38e7a7ada2a6ca5a60539a54b9c6810bdJungshik Jang            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
81549b47bbef8a8d27e9707d5d24848040519586a7aJinsuk Kim        }
81679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        mDeviceInfos.clear();
817fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang        updateSafeDeviceInfoList();
81879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
81979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
820a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
821c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo    // Seq #32
822ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang    void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
823ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang        assertRunOnServiceThread();
824c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo        if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
825c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo            setSystemAudioMode(false, true);
826c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
827c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo            return;
828c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo        }
82961f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang        HdmiDeviceInfo avr = getAvrDeviceInfo();
830ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang        if (avr == null) {
831c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo            setSystemAudioMode(false, true);
832d05f67f9721e1f9194a1f57cf7481b4be65366b3Yuncheol Heo            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
833ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang            return;
834ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang        }
835ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang
836ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang        addAndStartAction(
837ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang                new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
838ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang    }
839ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang
840ca5be9a8fbc91daece39c851ca3f09d60b24b870Jungshik Jang    // # Seq 25
8417ecfbaed6e902aea151bc1919cf7771bbd868fc4Jinsuk Kim    void setSystemAudioMode(boolean on, boolean updateSetting) {
8422e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang        HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on);
843473119fdc36340f833e4b755ae7f50a6914e0a24Jungshik Jang
844377dcbd53af4529c352d453424539b069909fce4Jungshik Jang        if (updateSetting) {
845377dcbd53af4529c352d453424539b069909fce4Jungshik Jang            mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on);
846377dcbd53af4529c352d453424539b069909fce4Jungshik Jang        }
847377dcbd53af4529c352d453424539b069909fce4Jungshik Jang        updateAudioManagerForSystemAudio(on);
84879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        synchronized (mLock) {
849377dcbd53af4529c352d453424539b069909fce4Jungshik Jang            if (mSystemAudioActivated != on) {
850377dcbd53af4529c352d453424539b069909fce4Jungshik Jang                mSystemAudioActivated = on;
851ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang                mService.announceSystemAudioModeChange(on);
85279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang            }
85379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
85479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
85579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
856377dcbd53af4529c352d453424539b069909fce4Jungshik Jang    private void updateAudioManagerForSystemAudio(boolean on) {
8576b096d349b8ea879ee152a50df66427c919adabaJungshik Jang        int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
8586b096d349b8ea879ee152a50df66427c919adabaJungshik Jang        HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
859377dcbd53af4529c352d453424539b069909fce4Jungshik Jang    }
860377dcbd53af4529c352d453424539b069909fce4Jungshik Jang
861377dcbd53af4529c352d453424539b069909fce4Jungshik Jang    boolean isSystemAudioActivated() {
86286a1e5a16ed6d49ecbdfe78c3ca7a9afc7814264Jinsuk Kim        if (!hasSystemAudioDevice()) {
863377dcbd53af4529c352d453424539b069909fce4Jungshik Jang            return false;
864377dcbd53af4529c352d453424539b069909fce4Jungshik Jang        }
86579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        synchronized (mLock) {
866377dcbd53af4529c352d453424539b069909fce4Jungshik Jang            return mSystemAudioActivated;
86779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
86879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
86979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
870377dcbd53af4529c352d453424539b069909fce4Jungshik Jang    boolean getSystemAudioModeSetting() {
871377dcbd53af4529c352d453424539b069909fce4Jungshik Jang        return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false);
872377dcbd53af4529c352d453424539b069909fce4Jungshik Jang    }
873377dcbd53af4529c352d453424539b069909fce4Jungshik Jang
87479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    /**
87579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * Change ARC status into the given {@code enabled} status.
87679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
87779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * @return {@code true} if ARC was in "Enabled" status
87879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     */
879a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    @ServiceThreadOnly
88079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    boolean setArcStatus(boolean enabled) {
881a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        assertRunOnServiceThread();
8826b096d349b8ea879ee152a50df66427c919adabaJungshik Jang
8836b096d349b8ea879ee152a50df66427c919adabaJungshik Jang        HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
884a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        boolean oldStatus = mArcEstablished;
885a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        // 1. Enable/disable ARC circuit.
886757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim        setAudioReturnChannel(enabled);
887a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        // 2. Notify arc status to audio service.
888a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        notifyArcStatusToAudioService(enabled);
889a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        // 3. Update arc status;
890a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        mArcEstablished = enabled;
891a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        return oldStatus;
89279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
89379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
894757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim    /**
895757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim     * Switch hardware ARC circuit in the system.
896757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim     */
897757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim    @ServiceThreadOnly
898757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim    void setAudioReturnChannel(boolean enabled) {
899757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim        assertRunOnServiceThread();
900757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim        HdmiDeviceInfo avr = getAvrDeviceInfo();
901757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim        if (avr != null) {
902757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim            mService.setAudioReturnChannel(avr.getPortId(), enabled);
903757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim        }
904757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim    }
905757c097753a166a6f3ecbbcbf42075ce3ba1a7edJinsuk Kim
9062ee0d6f44d7274bb1846cc6ff7a60451539a2b51Jinsuk Kim    @ServiceThreadOnly
9072ee0d6f44d7274bb1846cc6ff7a60451539a2b51Jinsuk Kim    private void updateArcFeatureStatus(int portId, boolean isConnected) {
9082ee0d6f44d7274bb1846cc6ff7a60451539a2b51Jinsuk Kim        assertRunOnServiceThread();
90937f5bc281c40965f8604400d700e68c0d17e77b0Jinsuk Kim        HdmiPortInfo portInfo = mService.getPortInfo(portId);
91037f5bc281c40965f8604400d700e68c0d17e77b0Jinsuk Kim        if (!portInfo.isArcSupported()) {
91137f5bc281c40965f8604400d700e68c0d17e77b0Jinsuk Kim            return;
91237f5bc281c40965f8604400d700e68c0d17e77b0Jinsuk Kim        }
913a6d5dad9bf1f43e2b4cdf18728a4b7a0d4d76bb5Jinsuk Kim        HdmiDeviceInfo avr = getAvrDeviceInfo();
914a6d5dad9bf1f43e2b4cdf18728a4b7a0d4d76bb5Jinsuk Kim        if (avr == null) {
91537f5bc281c40965f8604400d700e68c0d17e77b0Jinsuk Kim            if (isConnected) {
91637f5bc281c40965f8604400d700e68c0d17e77b0Jinsuk Kim                // Update the status (since TV may not have seen AVR yet) so
91737f5bc281c40965f8604400d700e68c0d17e77b0Jinsuk Kim                // that ARC can be initiated after discovery.
91837f5bc281c40965f8604400d700e68c0d17e77b0Jinsuk Kim                mArcFeatureEnabled.put(portId, isConnected);
91937f5bc281c40965f8604400d700e68c0d17e77b0Jinsuk Kim            }
920a6d5dad9bf1f43e2b4cdf18728a4b7a0d4d76bb5Jinsuk Kim            return;
921a6d5dad9bf1f43e2b4cdf18728a4b7a0d4d76bb5Jinsuk Kim        }
9222ee0d6f44d7274bb1846cc6ff7a60451539a2b51Jinsuk Kim        // HEAC 2.4, HEACT 5-15
9232ee0d6f44d7274bb1846cc6ff7a60451539a2b51Jinsuk Kim        // Should not activate ARC if +5V status is false.
92437f5bc281c40965f8604400d700e68c0d17e77b0Jinsuk Kim        if (avr.getPortId() == portId) {
9255bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim            changeArcFeatureEnabled(portId, isConnected);
9262ee0d6f44d7274bb1846cc6ff7a60451539a2b51Jinsuk Kim        }
9272ee0d6f44d7274bb1846cc6ff7a60451539a2b51Jinsuk Kim    }
9282ee0d6f44d7274bb1846cc6ff7a60451539a2b51Jinsuk Kim
9297b0cf6413218e5b5c549eea31733222fcffafabcJinsuk Kim    @ServiceThreadOnly
9307b0cf6413218e5b5c549eea31733222fcffafabcJinsuk Kim    boolean isConnected(int portId) {
9317b0cf6413218e5b5c549eea31733222fcffafabcJinsuk Kim        assertRunOnServiceThread();
9327b0cf6413218e5b5c549eea31733222fcffafabcJinsuk Kim        return mService.isConnected(portId);
9337b0cf6413218e5b5c549eea31733222fcffafabcJinsuk Kim    }
9347b0cf6413218e5b5c549eea31733222fcffafabcJinsuk Kim
935a858d221ff86c497e745222ea15bab141e337636Jungshik Jang    private void notifyArcStatusToAudioService(boolean enabled) {
936a858d221ff86c497e745222ea15bab141e337636Jungshik Jang        // Note that we don't set any name to ARC.
937a858d221ff86c497e745222ea15bab141e337636Jungshik Jang        mService.getAudioManager().setWiredDeviceConnectionState(
938a858d221ff86c497e745222ea15bab141e337636Jungshik Jang                AudioSystem.DEVICE_OUT_HDMI_ARC,
93910804eb2818ab59b763a37b4f6151693c2ebba7bPaul McLean                enabled ? 1 : 0, "", "");
940a858d221ff86c497e745222ea15bab141e337636Jungshik Jang    }
941a858d221ff86c497e745222ea15bab141e337636Jungshik Jang
94279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    /**
9435bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim     * Returns true if ARC is currently established on a certain port.
94479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     */
945a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    @ServiceThreadOnly
9465bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim    boolean isArcEstablished() {
947a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        assertRunOnServiceThread();
9485bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim        if (mArcEstablished) {
9495bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim            for (int i = 0; i < mArcFeatureEnabled.size(); i++) {
9505bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim                if (mArcFeatureEnabled.valueAt(i)) return true;
9515bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim            }
9525bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim        }
9535bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim        return false;
954a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    }
955a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang
956a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    @ServiceThreadOnly
9575bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim    void changeArcFeatureEnabled(int portId, boolean enabled) {
958a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        assertRunOnServiceThread();
959a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang
9605bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim        if (mArcFeatureEnabled.get(portId) != enabled) {
9615bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim            mArcFeatureEnabled.put(portId, enabled);
962a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            if (enabled) {
963a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang                if (!mArcEstablished) {
964a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang                    startArcAction(true);
965a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang                }
966a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            } else {
967a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang                if (mArcEstablished) {
968a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang                    startArcAction(false);
969a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang                }
970a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            }
971a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        }
972a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    }
973a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang
974a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    @ServiceThreadOnly
9755bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim    boolean isArcFeatureEnabled(int portId) {
976a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        assertRunOnServiceThread();
9775bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim        return mArcFeatureEnabled.get(portId);
978a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    }
979a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang
980a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang    @ServiceThreadOnly
981339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang    void startArcAction(boolean enabled) {
982a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        assertRunOnServiceThread();
98361f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang        HdmiDeviceInfo info = getAvrDeviceInfo();
984a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        if (info == null) {
985339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            Slog.w(TAG, "Failed to start arc action; No AVR device.");
986a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            return;
987a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        }
988339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
989339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
990339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
991339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
992339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            }
993a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            return;
994a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        }
995a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang
996a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        // Terminate opposite action and start action if not exist.
997a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        if (enabled) {
998a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            removeAction(RequestArcTerminationAction.class);
999a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            if (!hasAction(RequestArcInitiationAction.class)) {
1000a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang                addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
1001a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            }
1002a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang        } else {
1003a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            removeAction(RequestArcInitiationAction.class);
1004a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            if (!hasAction(RequestArcTerminationAction.class)) {
1005a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang                addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
1006a13da0d5913757e2456020c69481f98d0e44c090Jungshik Jang            }
100779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
100879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
100979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
1010339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang    private boolean isDirectConnectAddress(int physicalAddress) {
1011339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
1012339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang    }
1013339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang
101479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    void setAudioStatus(boolean mute, int volume) {
10158fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        synchronized (mLock) {
10168fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang            mSystemAudioMute = mute;
10178fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang            mSystemAudioVolume = volume;
1018b69aafbfaddd8a6ac84b366b5db640cdd7e95354Jungshik Jang            int maxVolume = mService.getAudioManager().getStreamMaxVolume(
1019b69aafbfaddd8a6ac84b366b5db640cdd7e95354Jungshik Jang                    AudioManager.STREAM_MUSIC);
1020b69aafbfaddd8a6ac84b366b5db640cdd7e95354Jungshik Jang            mService.setAudioStatus(mute,
1021b69aafbfaddd8a6ac84b366b5db640cdd7e95354Jungshik Jang                    VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
10222e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang            displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
10232e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang                    mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
10248fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        }
10258fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    }
10268fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang
10278fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    @ServiceThreadOnly
10288fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    void changeVolume(int curVolume, int delta, int maxVolume) {
10298fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        assertRunOnServiceThread();
1030377dcbd53af4529c352d453424539b069909fce4Jungshik Jang        if (delta == 0 || !isSystemAudioActivated()) {
10318fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang            return;
10328fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        }
10338fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang
10348fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        int targetVolume = curVolume + delta;
10358fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
10368fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        synchronized (mLock) {
10378fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang            // If new volume is the same as current system audio volume, just ignore it.
10388fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang            // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
10398fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang            if (cecVolume == mSystemAudioVolume) {
10408fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang                // Update tv volume with system volume value.
10418fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang                mService.setAudioStatus(false,
10428fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang                        VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
10438fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang                return;
10448fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang            }
10458fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        }
10468fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang
10472e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang        List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
10482e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang        if (actions.isEmpty()) {
10492e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang            addAndStartAction(new VolumeControlAction(this,
10502e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang                    getAvrDeviceInfo().getLogicalAddress(), delta > 0));
10512e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang        } else {
10522e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang            actions.get(0).handleVolumeChange(delta > 0);
10532e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang        }
10548fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    }
10558fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang
10568fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    @ServiceThreadOnly
10578fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    void changeMute(boolean mute) {
10588fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        assertRunOnServiceThread();
10596b096d349b8ea879ee152a50df66427c919adabaJungshik Jang        HdmiLogger.debug("[A]:Change mute:%b", mute);
1060720407ad06dbf4486ef6d724280640990d2fcd2aJungshik Jang        synchronized (mLock) {
1061720407ad06dbf4486ef6d724280640990d2fcd2aJungshik Jang            if (mSystemAudioMute == mute) {
1062720407ad06dbf4486ef6d724280640990d2fcd2aJungshik Jang                HdmiLogger.debug("No need to change mute.");
1063720407ad06dbf4486ef6d724280640990d2fcd2aJungshik Jang                return;
1064720407ad06dbf4486ef6d724280640990d2fcd2aJungshik Jang            }
1065720407ad06dbf4486ef6d724280640990d2fcd2aJungshik Jang        }
1066377dcbd53af4529c352d453424539b069909fce4Jungshik Jang        if (!isSystemAudioActivated()) {
10676b096d349b8ea879ee152a50df66427c919adabaJungshik Jang            HdmiLogger.debug("[A]:System audio is not activated.");
10688fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang            return;
10698fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        }
10708fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang
10718fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        // Remove existing volume action.
10728fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang        removeAction(VolumeControlAction.class);
10732e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang        sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
10742e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang                mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION :
10752e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang                        HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
10768fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang    }
10778fa36b110be29d92a9aba070fa4666eefb14b584Jungshik Jang
107879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    @Override
1079a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
108079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    protected boolean handleInitiateArc(HdmiCecMessage message) {
108179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
1082339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang
1083339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        if (!canStartArcUpdateAction(message.getSource(), true)) {
10847fa3a66470d2133796defd14a0600578758882acJinsuk Kim            if (getAvrDeviceInfo() == null) {
10857fa3a66470d2133796defd14a0600578758882acJinsuk Kim                // AVR may not have been discovered yet. Delay the message processing.
10867fa3a66470d2133796defd14a0600578758882acJinsuk Kim                mDelayedMessageBuffer.add(message);
10877fa3a66470d2133796defd14a0600578758882acJinsuk Kim                return true;
10887fa3a66470d2133796defd14a0600578758882acJinsuk Kim            }
1089339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1090339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            if (!isConnectedToArcPort(message.getSource())) {
1091339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
1092339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            }
1093339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            return true;
1094339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        }
1095339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang
109679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        // In case where <Initiate Arc> is started by <Request ARC Initiation>
109779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        // need to clean up RequestArcInitiationAction.
109879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        removeAction(RequestArcInitiationAction.class);
109979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
110079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang                message.getSource(), true);
110179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        addAndStartAction(action);
110279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        return true;
110379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
110479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
1105339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang    private boolean canStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled) {
1106339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        HdmiDeviceInfo avr = getAvrDeviceInfo();
1107339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        if (avr != null
1108339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang                && (avrAddress == avr.getLogicalAddress())
1109339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang                && isConnectedToArcPort(avr.getPhysicalAddress())
1110339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang                && isDirectConnectAddress(avr.getPhysicalAddress())) {
1111339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            if (shouldCheckArcFeatureEnabled) {
11125bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim                return isArcFeatureEnabled(avr.getPortId());
1113339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            } else {
1114339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang                return true;
1115339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            }
1116339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        } else {
1117339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang            return false;
1118339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        }
1119339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang    }
1120339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang
112179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    @Override
1122a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
112379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    protected boolean handleTerminateArc(HdmiCecMessage message) {
112479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
11257e4b480a0bf9f93a428f7c46bfea77ebfdb92d40Jinsuk Kim        if (mService .isPowerStandbyOrTransient()) {
11267e4b480a0bf9f93a428f7c46bfea77ebfdb92d40Jinsuk Kim            setArcStatus(false);
11277e4b480a0bf9f93a428f7c46bfea77ebfdb92d40Jinsuk Kim            return true;
11287e4b480a0bf9f93a428f7c46bfea77ebfdb92d40Jinsuk Kim        }
11297e4b480a0bf9f93a428f7c46bfea77ebfdb92d40Jinsuk Kim        // Do not check ARC configuration since the AVR might have been already removed.
11307e4b480a0bf9f93a428f7c46bfea77ebfdb92d40Jinsuk Kim        // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by
11317e4b480a0bf9f93a428f7c46bfea77ebfdb92d40Jinsuk Kim        // <Request ARC Termination>.
113279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        removeAction(RequestArcTerminationAction.class);
113379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
113479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang                message.getSource(), false);
113579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        addAndStartAction(action);
113679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        return true;
113779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
113879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
113979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    @Override
1140a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
114179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
114279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
114379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        if (!isMessageForSystemAudio(message)) {
1144ad1e3d7df42376cd2a257b6c3b2fed540658a6e3Jinsuk Kim            if (getAvrDeviceInfo() == null) {
1145ad1e3d7df42376cd2a257b6c3b2fed540658a6e3Jinsuk Kim                // AVR may not have been discovered yet. Delay the message processing.
1146ad1e3d7df42376cd2a257b6c3b2fed540658a6e3Jinsuk Kim                mDelayedMessageBuffer.add(message);
1147ad1e3d7df42376cd2a257b6c3b2fed540658a6e3Jinsuk Kim                return true;
1148ad1e3d7df42376cd2a257b6c3b2fed540658a6e3Jinsuk Kim            }
11492e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang            HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
11504480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
11514480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang            return true;
115279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
115379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
11547f0a1c54a837da7ebbb6f7eb56f44894e0df22c2Jungshik Jang                message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
115579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        addAndStartAction(action);
115679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        return true;
115779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
115879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
115979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    @Override
1160a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
116179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
116279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
116379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        if (!isMessageForSystemAudio(message)) {
11642e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang            HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
11654480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang            // Ignore this message.
11664480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang            return true;
116779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
11687ecfbaed6e902aea151bc1919cf7771bbd868fc4Jinsuk Kim        setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true);
116979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        return true;
117079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
117179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
1172b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    // Seq #53
1173b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    @Override
1174b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    @ServiceThreadOnly
1175b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    protected boolean handleRecordTvScreen(HdmiCecMessage message) {
1176b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
1177b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        if (!actions.isEmpty()) {
1178b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            // Assumes only one OneTouchRecordAction.
1179b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            OneTouchRecordAction action = actions.get(0);
1180b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            if (action.getRecorderAddress() != message.getSource()) {
118112e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang                announceOneTouchRecordResult(
1182326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                        message.getSource(),
118312e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang                        HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
1184b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            }
1185b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            return super.handleRecordTvScreen(message);
1186b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        }
1187b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1188b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        int recorderAddress = message.getSource();
1189b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
11904480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        int reason = startOneTouchRecord(recorderAddress, recordSource);
11914480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        if (reason != Constants.ABORT_NO_ERROR) {
11924480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang            mService.maySendFeatureAbortCommand(message, reason);
11934480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        }
1194b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        return true;
1195b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    }
1196b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1197e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang    @Override
1198e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang    protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
1199e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        byte[] params = message.getParams();
1200e9e0f070e34a612fb3bab5d97bdec1e266da4a20Jungshik Jang        int timerClearedStatusData = params[0] & 0xFF;
1201326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang        announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
1202e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        return true;
1203e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang    }
1204e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang
1205326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang    void announceOneTouchRecordResult(int recorderAddress, int result) {
1206326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang        mService.invokeOneTouchRecordResult(recorderAddress, result);
120712e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang    }
120812e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang
1209326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang    void announceTimerRecordingResult(int recorderAddress, int result) {
1210326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang        mService.invokeTimerRecordingResult(recorderAddress, result);
121112e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang    }
121212e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang
1213326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang    void announceClearTimerRecordingResult(int recorderAddress, int result) {
1214326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang        mService.invokeClearTimerRecordingResult(recorderAddress, result);
1215faa49bc896be859d5bcf2da3bddd4507b5e6494cJungshik Jang    }
1216faa49bc896be859d5bcf2da3bddd4507b5e6494cJungshik Jang
121779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    private boolean isMessageForSystemAudio(HdmiCecMessage message) {
12184480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        return mService.isControlEnabled()
12194480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang                && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
1220473119fdc36340f833e4b755ae7f50a6914e0a24Jungshik Jang                && (message.getDestination() == Constants.ADDR_TV
1221473119fdc36340f833e4b755ae7f50a6914e0a24Jungshik Jang                        || message.getDestination() == Constants.ADDR_BROADCAST)
1222473119fdc36340f833e4b755ae7f50a6914e0a24Jungshik Jang                && getAvrDeviceInfo() != null;
122379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
122479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
122579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    /**
122661f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
122779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * logical address as new device info's.
122879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
122979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
123079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
123161f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
123261f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
123379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *         that has the same logical address as new one has.
123479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     */
1235a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
123661f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang    private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
123779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
12388960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
123979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        if (oldDeviceInfo != null) {
12408960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            removeDeviceInfo(deviceInfo.getId());
124179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
12428960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
1243fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang        updateSafeDeviceInfoList();
124479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        return oldDeviceInfo;
124579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
124679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
124779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    /**
124879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * Remove a device info corresponding to the given {@code logicalAddress}.
124961f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * It returns removed {@link HdmiDeviceInfo} if exists.
125079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
125179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
125279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
12538960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim     * @param id id of device to be removed
125461f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
125579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     */
1256a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
12578960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim    private HdmiDeviceInfo removeDeviceInfo(int id) {
125879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
12598960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
126079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        if (deviceInfo != null) {
12618960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            mDeviceInfos.remove(id);
126279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
1263fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang        updateSafeDeviceInfoList();
126479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        return deviceInfo;
126579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
126679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
126779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    /**
126861f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * Return a list of all {@link HdmiDeviceInfo}.
126979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
127079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1271339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang     * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
12728e93c84739902f5adaa499b474f39e3c4807bc1cJungshik Jang     * does not include local device.
127379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     */
1274a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
12754b4b940c69d22304eb0734ed2432fc70ebadd462Jinsuk Kim    List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
127679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
12774b4b940c69d22304eb0734ed2432fc70ebadd462Jinsuk Kim        if (includeLocalDevice) {
1278fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang            return HdmiUtils.sparseArrayToList(mDeviceInfos);
127979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        } else {
128061f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang            ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
128179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang            for (int i = 0; i < mDeviceInfos.size(); ++i) {
128261f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang                HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
128379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang                if (!isLocalDeviceAddress(info.getLogicalAddress())) {
128479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang                    infoList.add(info);
128579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang                }
128679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang            }
128779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang            return infoList;
128879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
128979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
129079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
1291fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    /**
12929c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim     * Return external input devices.
1293fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang     */
1294ed0864557b3340ab7db00e2dc95b29c4b8bb485dJinsuk Kim    List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
1295ed0864557b3340ab7db00e2dc95b29c4b8bb485dJinsuk Kim        return mSafeExternalInputs;
1296fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    }
1297fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang
1298a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
1299fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    private void updateSafeDeviceInfoList() {
1300fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang        assertRunOnServiceThread();
130161f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang        List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
130261f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang        List<HdmiDeviceInfo> externalInputs = getInputDevices();
1303fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang        synchronized (mLock) {
1304fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang            mSafeAllDeviceInfos = copiedDevices;
13059c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim            mSafeExternalInputs = externalInputs;
13069c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim        }
13079c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim    }
13089c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim
13099c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim    /**
13109c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim     * Return a list of external cec input (source) devices.
13119c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim     *
13129c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim     * <p>Note that this effectively excludes non-source devices like system audio,
13139c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim     * secondary TV.
13149c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim     */
131561f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang    private List<HdmiDeviceInfo> getInputDevices() {
131661f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
13179c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim        for (int i = 0; i < mDeviceInfos.size(); ++i) {
131861f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang            HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
13198960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            if (isLocalDeviceAddress(info.getLogicalAddress())) {
13209c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim                continue;
13219c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim            }
13224fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
13239c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim                infoList.add(info);
13249c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim            }
1325fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang        }
13269c37e1f53ea4734bfe5ae156dc5399ce5f2c7cccJinsuk Kim        return infoList;
1327fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    }
1328fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang
13294fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
13304fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    // Returns true if the policy is set to true, and the device to check does not have
13314fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    // a parent CEC device (which should be the CEC-enabled switch) in the list.
133261f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang    private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
13334fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
13344fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim                && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
13354fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    }
13364fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim
13374fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
13384fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        for (int switchPath : switches) {
13394fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            if (isParentPath(switchPath, path)) {
13404fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim                return true;
13414fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            }
13424fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        }
13434fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        return false;
13444fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    }
13454fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim
13464fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    private static boolean isParentPath(int parentPath, int childPath) {
13474fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
13484fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        // If child's last non-zero nibble is removed, the result equals to the parent.
13494fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        for (int i = 0; i <= 12; i += 4) {
13504fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            int nibble = (childPath >> i) & 0xF;
13514fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            if (nibble != 0) {
13524fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim                int parentNibble = (parentPath >> i) & 0xF;
13534fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim                return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
13544fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            }
13554fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        }
13564fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        return false;
13574fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    }
13584fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim
135961daf6b38e7a7ada2a6ca5a60539a54b9c6810bdJungshik Jang    private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
136098d760e1c3123e6db8459f605d59a5689d56268cJinsuk Kim        if (!hideDevicesBehindLegacySwitch(info)) {
136161daf6b38e7a7ada2a6ca5a60539a54b9c6810bdJungshik Jang            mService.invokeDeviceEventListeners(info, status);
13624fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        }
13634fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    }
13644fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim
136579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    private boolean isLocalDeviceAddress(int address) {
1366bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        return mLocalDeviceAddresses.contains(address);
136779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
136879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
1369e9cf1583c74fd03977c1ecb14520663710f14439Jungshik Jang    @ServiceThreadOnly
137061f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang    HdmiDeviceInfo getAvrDeviceInfo() {
1371e9cf1583c74fd03977c1ecb14520663710f14439Jungshik Jang        assertRunOnServiceThread();
13728960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1373e9cf1583c74fd03977c1ecb14520663710f14439Jungshik Jang    }
1374e9cf1583c74fd03977c1ecb14520663710f14439Jungshik Jang
137579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    /**
137661f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
137779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
13788960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim     * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
137979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
1380339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang     * @param logicalAddress logical address of the device to be retrieved
138161f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
138279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *         Returns null if no logical address matched
138379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     */
1384a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
13858960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim    HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
138679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
13878960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
138879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
138979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
1390e9cf1583c74fd03977c1ecb14520663710f14439Jungshik Jang    boolean hasSystemAudioDevice() {
1391e9cf1583c74fd03977c1ecb14520663710f14439Jungshik Jang        return getSafeAvrDeviceInfo() != null;
1392e9cf1583c74fd03977c1ecb14520663710f14439Jungshik Jang    }
1393e9cf1583c74fd03977c1ecb14520663710f14439Jungshik Jang
139461f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang    HdmiDeviceInfo getSafeAvrDeviceInfo() {
13958960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
139679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
139779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
139879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    /**
13998960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim     * Thread safe version of {@link #getCecDeviceInfo(int)}.
1400fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang     *
1401fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang     * @param logicalAddress logical address to be retrieved
140261f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1403fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang     *         Returns null if no logical address matched
1404fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang     */
14058960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim    HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
1406fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang        synchronized (mLock) {
14078960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
14088960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim                if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
14098960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim                    return info;
14108960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim                }
14118960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            }
14128960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim            return null;
1413fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang        }
1414fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    }
1415fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang
1416bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim    List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
1417bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1418bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1419bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim            if (isLocalDeviceAddress(info.getLogicalAddress())) {
1420bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim                continue;
1421bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim            }
1422bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim            infoList.add(info);
1423bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        }
1424bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim        return infoList;
1425bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim    }
1426bdf27fbf746bee11430c4db2ea6dfd026bae77feJinsuk Kim
1427fa8e90db6a84ce1b19af86d46b547664d8a7ac37Jungshik Jang    /**
14288f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang     * Called when a device is newly added or a new device is detected or
14298f2ed357a23fac4a55da43d20138b438b4ac79a7Jungshik Jang     * existing device is updated.
143079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
143179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * @param info device info of a new device.
143279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     */
1433a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
143461f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang    final void addCecDevice(HdmiDeviceInfo info) {
143579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
14366102bac706d351ce52ce385745f30aecaf835a2aJinsuk Kim        HdmiDeviceInfo old = addDeviceInfo(info);
143713c030e828a90fcfc57b52024b72326757cec583Jinsuk Kim        if (info.getLogicalAddress() == mAddress) {
143813c030e828a90fcfc57b52024b72326757cec583Jinsuk Kim            // The addition of TV device itself should not be notified.
143913c030e828a90fcfc57b52024b72326757cec583Jinsuk Kim            return;
144013c030e828a90fcfc57b52024b72326757cec583Jinsuk Kim        }
14416102bac706d351ce52ce385745f30aecaf835a2aJinsuk Kim        if (old == null) {
14426102bac706d351ce52ce385745f30aecaf835a2aJinsuk Kim            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
14436102bac706d351ce52ce385745f30aecaf835a2aJinsuk Kim        } else if (!old.equals(info)) {
14444fcbf0b93e15e60eeb8b08a32d895de07c73cb1bJinsuk Kim            invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
14454fcbf0b93e15e60eeb8b08a32d895de07c73cb1bJinsuk Kim            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
14466102bac706d351ce52ce385745f30aecaf835a2aJinsuk Kim        }
144779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
144879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
144979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    /**
145079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * Called when a device is removed or removal of device is detected.
145179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
145279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * @param address a logical address of a device to be removed
145379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     */
1454a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
145579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    final void removeCecDevice(int address) {
145679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
14578960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
145826dc71e7feefb2417fd5f007af68614c1c197cf8Jungshik Jang
145979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        mCecMessageCache.flushMessagesFrom(address);
146061daf6b38e7a7ada2a6ca5a60539a54b9c6810bdJungshik Jang        invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
146179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
146279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
146326dc71e7feefb2417fd5f007af68614c1c197cf8Jungshik Jang    @ServiceThreadOnly
146426dc71e7feefb2417fd5f007af68614c1c197cf8Jungshik Jang    void handleRemoveActiveRoutingPath(int path) {
146526dc71e7feefb2417fd5f007af68614c1c197cf8Jungshik Jang        assertRunOnServiceThread();
146692b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        // Seq #23
146792b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        if (isTailOfActivePath(path, getActivePath())) {
146892b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim            int newPath = mService.portIdToPath(getActivePortId());
1469546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim            startRoutingControl(getActivePath(), newPath, true, null);
147092b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        }
147192b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    }
147292b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim
14735344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim    /**
14745344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim     * Launch routing control process.
14755344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim     *
14765344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim     * @param routingForBootup true if routing control is initiated due to One Touch Play
14775344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim     *        or TV power on
14785344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim     */
147992b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    @ServiceThreadOnly
14805344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim    void launchRoutingControl(boolean routingForBootup) {
148192b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        assertRunOnServiceThread();
148292b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        // Seq #24
1483c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim        if (getActivePortId() != Constants.INVALID_PORT_ID) {
14845344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim            if (!routingForBootup && !isProhibitMode()) {
14855344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim                int newPath = mService.portIdToPath(getActivePortId());
14865344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim                setActivePath(newPath);
1487546d867cb89099ed9036d54d1d49e11748c9a27eJinsuk Kim                startRoutingControl(getActivePath(), newPath, routingForBootup, null);
14885344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim            }
148992b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        } else {
149092b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim            int activePath = mService.getPhysicalAddress();
149192b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim            setActivePath(activePath);
14922ab6d9fff36836c71bc0ee4afa25c11b48a9bd99Jinsuk Kim            if (!routingForBootup
14932ab6d9fff36836c71bc0ee4afa25c11b48a9bd99Jinsuk Kim                    && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
14945344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim                mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
14955344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim                        activePath));
14965344cd98e69f92e70d52969b1851c9d8f9e81853Jinsuk Kim            }
149792b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim        }
149892b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    }
149992b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim
150079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    /**
150161f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
150279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * the given routing path. CEC devices use routing path for its physical address to
150379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * describe the hierarchy of the devices in the network.
150479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
150579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * @param path routing path or physical address
150661f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang     * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
150779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     */
1508a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
150961f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang    final HdmiDeviceInfo getDeviceInfoByPath(int path) {
151079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
151161f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang        for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
151279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang            if (info.getPhysicalAddress() == path) {
151379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang                return info;
151479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang            }
151579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
151679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        return null;
151779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
151879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
151979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    /**
15207640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim     * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
15217640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim     * the given routing path. This is the version accessible safely from threads
15227640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim     * other than service thread.
15237640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim     *
15247640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim     * @param path routing path or physical address
15257640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim     * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
15267640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim     */
15277640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim    HdmiDeviceInfo getSafeDeviceInfoByPath(int path) {
15287640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim        synchronized (mLock) {
15297640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim            for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
15307640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim                if (info.getPhysicalAddress() == path) {
15317640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim                    return info;
15327640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim                }
15337640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim            }
15347640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim            return null;
15357640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim        }
15367640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim    }
15377640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim
15387640d9895cf8fae7a99a7db5bba0079ba6022621Jinsuk Kim    /**
153979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * Whether a device of the specified physical address and logical address exists
154079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * in a device info list. However, both are minimal condition and it could
154179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * be different device from the original one.
154279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     *
154379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * @param logicalAddress logical address of a device to be searched
154492b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim     * @param physicalAddress physical address of a device to be searched
154579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     * @return true if exist; otherwise false
154679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang     */
1547a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
15487cd4a589af7cc6e6880799e86ef6febca5add46dJinsuk Kim    boolean isInDeviceList(int logicalAddress, int physicalAddress) {
154979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
15508960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
155179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        if (device == null) {
155279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang            return false;
155379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
155479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        return device.getPhysicalAddress() == physicalAddress;
155579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    }
155679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
155779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang    @Override
1558a5b7414970c85217e88015e78ecbc5ba093dead3Jungshik Jang    @ServiceThreadOnly
155992b77cf9cbf512e7141cad6fef5a38d0682dde43Jinsuk Kim    void onHotplug(int portId, boolean connected) {
156079c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        assertRunOnServiceThread();
156179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang
15624fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        if (!connected) {
15634fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            removeCecSwitches(portId);
15644fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        }
156579c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        // Tv device will have permanent HotplugDetectionAction.
156679c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
156779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        if (!hotplugActions.isEmpty()) {
156879c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang            // Note that hotplug action is single action running on a machine.
156979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang            // "pollAllDevicesNow" cleans up timer and start poll action immediately.
157024c23c1dd82870be4c09f3c5b6ae354de722de94Jungshik Jang            // It covers seq #40, #43.
157179c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang            hotplugActions.get(0).pollAllDevicesNow();
157279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang        }
15732ee0d6f44d7274bb1846cc6ff7a60451539a2b51Jinsuk Kim        updateArcFeatureStatus(portId, connected);
157460cffce420db4c3395f86d3b9bb36003adf26f5dJungshik Jang    }
1575160a6e5b99de15ce755e2e5521dce32d81ab180aJinsuk Kim
15764fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    private void removeCecSwitches(int portId) {
15774fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        Iterator<Integer> it = mCecSwitches.iterator();
15784fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        while (!it.hasNext()) {
15794fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            int path = it.next();
15804fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            if (pathToPortId(path) == portId) {
15814fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim                it.remove();
15824fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim            }
15834fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim        }
15844fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim    }
15854fdd0f47bc10bdbc429c6316c2cc98a747570b12Jinsuk Kim
1586e6e8f3d589f42393cf02a2bd766d678d80dad874Jinsuk Kim    @Override
1587160a6e5b99de15ce755e2e5521dce32d81ab180aJinsuk Kim    @ServiceThreadOnly
1588160a6e5b99de15ce755e2e5521dce32d81ab180aJinsuk Kim    void setAutoDeviceOff(boolean enabled) {
1589160a6e5b99de15ce755e2e5521dce32d81ab180aJinsuk Kim        assertRunOnServiceThread();
1590160a6e5b99de15ce755e2e5521dce32d81ab180aJinsuk Kim        mAutoDeviceOff = enabled;
1591544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim    }
1592544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim
1593544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim    @ServiceThreadOnly
1594544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim    void setAutoWakeup(boolean enabled) {
1595544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim        assertRunOnServiceThread();
1596544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim        mAutoWakeup = enabled;
1597160a6e5b99de15ce755e2e5521dce32d81ab180aJinsuk Kim    }
159838db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo
159925c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo    @ServiceThreadOnly
160025c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo    boolean getAutoWakeup() {
160125c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo        assertRunOnServiceThread();
160225c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo        return mAutoWakeup;
160325c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo    }
160425c20298ad04e0e591e0cfdc0bb9d01a985433abYuncheol Heo
160538db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    @Override
160638db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    @ServiceThreadOnly
16074fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang    protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
160838db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        assertRunOnServiceThread();
16097fa3a66470d2133796defd14a0600578758882acJinsuk Kim        mService.unregisterTvInputCallback(mTvInputCallback);
161038db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        // Remove any repeated working actions.
161138db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        // HotplugDetectionAction will be reinstated during the wake up process.
161238db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
161338db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
16144fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        removeAction(DeviceDiscoveryAction.class);
161538db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        removeAction(HotplugDetectionAction.class);
1616410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang        removeAction(PowerStatusMonitorAction.class);
1617faa49bc896be859d5bcf2da3bddd4507b5e6494cJungshik Jang        // Remove recording actions.
1618b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        removeAction(OneTouchRecordAction.class);
1619faa49bc896be859d5bcf2da3bddd4507b5e6494cJungshik Jang        removeAction(TimerRecordingAction.class);
16204fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang
16214fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        disableSystemAudioIfExist();
16224fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        disableArcIfExist();
1623a5445ce992a4e8ac5252975acedf3e5aec53867aJinsuk Kim
1624a5445ce992a4e8ac5252975acedf3e5aec53867aJinsuk Kim        super.disableDevice(initiatedByCec, callback);
162549b47bbef8a8d27e9707d5d24848040519586a7aJinsuk Kim        clearDeviceInfoList();
16267cc51c631d6e7e5680ce661089524b7335d88756Jinsuk Kim        getActiveSource().invalidate();
16277cc51c631d6e7e5680ce661089524b7335d88756Jinsuk Kim        setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
162838db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        checkIfPendingActionsCleared();
162938db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    }
163038db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo
16314fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang    @ServiceThreadOnly
16324fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang    private void disableSystemAudioIfExist() {
16334fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        assertRunOnServiceThread();
16344fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        if (getAvrDeviceInfo() == null) {
16354fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang            return;
16364fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        }
16374fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang
16384fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        // Seq #31.
16394fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        removeAction(SystemAudioActionFromAvr.class);
16404fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        removeAction(SystemAudioActionFromTv.class);
16414fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        removeAction(SystemAudioAutoInitiationAction.class);
16424fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        removeAction(SystemAudioStatusAction.class);
16434fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        removeAction(VolumeControlAction.class);
16444fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang    }
16454fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang
16464fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang    @ServiceThreadOnly
16474fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang    private void disableArcIfExist() {
16484fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        assertRunOnServiceThread();
164961f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang        HdmiDeviceInfo avr = getAvrDeviceInfo();
16504fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        if (avr == null) {
16514fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang            return;
16524fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        }
16534fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang
16544fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        // Seq #44.
16554fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        removeAction(RequestArcInitiationAction.class);
16565bcf5bf6b4203629e153dcb0646596e9b3f7c7c2Jinsuk Kim        if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
16574fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang            addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
16584fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang        }
16594fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang    }
16604fc1d105fc279bf7df6c876e160672866bdad8e7Jungshik Jang
166138db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    @Override
166238db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    @ServiceThreadOnly
1663e6e8f3d589f42393cf02a2bd766d678d80dad874Jinsuk Kim    protected void onStandby(boolean initiatedByCec, int standbyAction) {
166438db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        assertRunOnServiceThread();
166538db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        // Seq #11
166638db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        if (!mService.isControlEnabled()) {
166738db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo            return;
166838db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        }
1669544b62bb863788727587ee292596451e461fc0a7Jinsuk Kim        if (!initiatedByCec && mAutoDeviceOff) {
167038db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo            mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1671c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim                    mAddress, Constants.ADDR_BROADCAST));
167238db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo        }
167338db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo    }
167438db629d897e9d7c8e31ce0a7e985981e3e12996Yuncheol Heo
16754d43d93743222311c6377d4904c19ccb93699d3bJinsuk Kim    boolean isProhibitMode() {
16764d43d93743222311c6377d4904c19ccb93699d3bJinsuk Kim        return mService.isProhibitMode();
16774d43d93743222311c6377d4904c19ccb93699d3bJinsuk Kim    }
1678b38cd68b240d99e8c8ae6ff1802a574696b420cdJinsuk Kim
1679b38cd68b240d99e8c8ae6ff1802a574696b420cdJinsuk Kim    boolean isPowerStandbyOrTransient() {
1680b38cd68b240d99e8c8ae6ff1802a574696b420cdJinsuk Kim        return mService.isPowerStandbyOrTransient();
16818866c810b6778714da69ebc023ed432491caad92Jungshik Jang    }
1682c7eba0f1db8928ca779933a564a06989e22a8532Jinsuk Kim
1683339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang    @ServiceThreadOnly
1684c7eba0f1db8928ca779933a564a06989e22a8532Jinsuk Kim    void displayOsd(int messageId) {
1685339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        assertRunOnServiceThread();
1686339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang        mService.displayOsd(messageId);
1687b38cd68b240d99e8c8ae6ff1802a574696b420cdJinsuk Kim    }
1688b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
16892e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang    @ServiceThreadOnly
16902e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang    void displayOsd(int messageId, int extra) {
16912e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang        assertRunOnServiceThread();
16922e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang        mService.displayOsd(messageId, extra);
16932e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang    }
16942e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang
1695b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    // Seq #54 and #55
1696b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    @ServiceThreadOnly
16974480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang    int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1698b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        assertRunOnServiceThread();
1699b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        if (!mService.isControlEnabled()) {
1700b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1701326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
17024480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang            return Constants.ABORT_NOT_IN_CORRECT_MODE;
1703b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        }
1704b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1705b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        if (!checkRecorder(recorderAddress)) {
1706b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1707326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            announceOneTouchRecordResult(recorderAddress,
1708326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
17094480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang            return Constants.ABORT_NOT_IN_CORRECT_MODE;
1710b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        }
1711b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1712b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        if (!checkRecordSource(recordSource)) {
1713b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1714326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            announceOneTouchRecordResult(recorderAddress,
1715326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                    ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
1716d47abefc8269dae7fdfa2bb102bcb89cbea7c7b0Jinsuk Kim            return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1717b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        }
1718b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1719b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
1720b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
1721b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang                + Arrays.toString(recordSource));
17224480efa05aa5dd44f1432c3260be263546daf838Jungshik Jang        return Constants.ABORT_NO_ERROR;
1723b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    }
1724b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1725b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    @ServiceThreadOnly
1726b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    void stopOneTouchRecord(int recorderAddress) {
1727b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        assertRunOnServiceThread();
1728b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        if (!mService.isControlEnabled()) {
1729b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
1730326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1731b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            return;
1732b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        }
1733b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1734b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        if (!checkRecorder(recorderAddress)) {
1735b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1736326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            announceOneTouchRecordResult(recorderAddress,
1737326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1738b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang            return;
1739b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        }
1740b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1741b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        // Remove one touch record action so that other one touch record can be started.
1742b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        removeAction(OneTouchRecordAction.class);
1743b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress));
1744b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
1745b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    }
1746b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1747b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    private boolean checkRecorder(int recorderAddress) {
17488960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
1749b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        return (device != null)
1750b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang                && (HdmiUtils.getTypeFromAddress(recorderAddress)
175161f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang                        == HdmiDeviceInfo.DEVICE_RECORDER);
1752b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    }
1753b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1754b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    private boolean checkRecordSource(byte[] recordSource) {
1755b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
1756b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    }
1757b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1758b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    @ServiceThreadOnly
1759b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1760b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        assertRunOnServiceThread();
176112e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang        if (!mService.isControlEnabled()) {
176212e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1763326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            announceTimerRecordingResult(recorderAddress,
1764326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                    TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
176512e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang            return;
176612e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang        }
1767b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
176812e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang        if (!checkRecorder(recorderAddress)) {
176912e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1770326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            announceTimerRecordingResult(recorderAddress,
1771e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                    TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
177212e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang            return;
177312e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang        }
177412e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang
177512e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang        if (!checkTimerRecordingSource(sourceType, recordSource)) {
177612e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
177712e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang            announceTimerRecordingResult(
1778326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                    recorderAddress,
1779e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                    TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
178012e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang            return;
178112e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang        }
178212e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang
178312e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang        addAndStartAction(
178412e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang                new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
178512e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang        Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
178612e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang                + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
178712e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang    }
178812e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang
178912e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang    private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
179012e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang        return (recordSource != null)
179112e5dcefe136b58562f39604e6a8460ac92cb895Jungshik Jang                && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
1792b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    }
1793b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1794b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    @ServiceThreadOnly
1795b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1796b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang        assertRunOnServiceThread();
1797e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        if (!mService.isControlEnabled()) {
1798e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1799326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
1800e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            return;
1801e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        }
1802e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang
1803e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        if (!checkRecorder(recorderAddress)) {
1804e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1805326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            announceClearTimerRecordingResult(recorderAddress,
1806326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                    CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
1807e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            return;
1808e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        }
1809e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang
1810e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        if (!checkTimerRecordingSource(sourceType, recordSource)) {
1811e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1812326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            announceClearTimerRecordingResult(recorderAddress,
1813326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                    CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1814e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            return;
1815e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        }
1816e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang
1817e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        sendClearTimerMessage(recorderAddress, sourceType, recordSource);
1818e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang    }
1819e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang
1820326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang    private void sendClearTimerMessage(final int recorderAddress, int sourceType,
1821326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang            byte[] recordSource) {
1822e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        HdmiCecMessage message = null;
1823e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        switch (sourceType) {
1824e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            case TIMER_RECORDING_TYPE_DIGITAL:
1825e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress,
1826e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                        recordSource);
1827e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                break;
1828e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            case TIMER_RECORDING_TYPE_ANALOGUE:
1829e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress,
1830e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                        recordSource);
1831e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                break;
1832e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            case TIMER_RECORDING_TYPE_EXTERNAL:
1833e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress,
1834e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                        recordSource);
1835e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                break;
1836e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            default:
1837e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                Slog.w(TAG, "Invalid source type:" + recorderAddress);
1838326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                announceClearTimerRecordingResult(recorderAddress,
1839326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                        CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1840e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                return;
1841b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang
1842e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        }
1843e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        mService.sendCecCommand(message, new SendMessageCallback() {
1844e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            @Override
1845e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            public void onSendCompleted(int error) {
1846e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                if (error != Constants.SEND_RESULT_SUCCESS) {
1847326aef0c9402742e29c4503c857f93e75cf9a6ecJungshik Jang                    announceClearTimerRecordingResult(recorderAddress,
1848faa49bc896be859d5bcf2da3bddd4507b5e6494cJungshik Jang                            CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1849e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang                }
1850e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang            }
1851e5a9337ebe738633cf7b66141cdf76efcdc5754cJungshik Jang        });
1852b6591b8e5399099dc6b7693e0fc719b613aba89cJungshik Jang    }
1853410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang
1854410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang    void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
18558960d1b1552729e3dfd33deee951ac75933ad8e5Jinsuk Kim        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
1856410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang        if (info == null) {
1857410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang            Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
1858410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang            return;
1859410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang        }
1860410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang
1861410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang        if (info.getDevicePowerStatus() == newPowerStatus) {
1862410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang            return;
1863410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang        }
1864410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang
1865410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang        HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
1866410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang        // addDeviceInfo replaces old device info with new one if exists.
1867410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang        addDeviceInfo(newInfo);
1868410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang
186961daf6b38e7a7ada2a6ca5a60539a54b9c6810bdJungshik Jang        invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
1870410ca9c7a4a2d69af5c81e76320433bfda05cafeJungshik Jang    }
1871959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo
1872959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo    @Override
1873184b124ec22a796327642e3546d366179e693f07Yuncheol Heo    protected boolean handleMenuStatus(HdmiCecMessage message) {
1874184b124ec22a796327642e3546d366179e693f07Yuncheol Heo        // Do nothing and just return true not to prevent from responding <Feature Abort>.
1875184b124ec22a796327642e3546d366179e693f07Yuncheol Heo        return true;
1876184b124ec22a796327642e3546d366179e693f07Yuncheol Heo    }
1877184b124ec22a796327642e3546d366179e693f07Yuncheol Heo
1878184b124ec22a796327642e3546d366179e693f07Yuncheol Heo    @Override
1879d4a94db1cd44a536d535de890a0a14919a39a0dcJinsuk Kim    protected void sendStandby(int deviceId) {
1880d4a94db1cd44a536d535de890a0a14919a39a0dcJinsuk Kim        HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
1881d4a94db1cd44a536d535de890a0a14919a39a0dcJinsuk Kim        if (targetDevice == null) {
1882d4a94db1cd44a536d535de890a0a14919a39a0dcJinsuk Kim            return;
1883d4a94db1cd44a536d535de890a0a14919a39a0dcJinsuk Kim        }
1884d4a94db1cd44a536d535de890a0a14919a39a0dcJinsuk Kim        int targetAddress = targetDevice.getLogicalAddress();
1885d4a94db1cd44a536d535de890a0a14919a39a0dcJinsuk Kim        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
1886d4a94db1cd44a536d535de890a0a14919a39a0dcJinsuk Kim    }
1887d4a94db1cd44a536d535de890a0a14919a39a0dcJinsuk Kim
18887fa3a66470d2133796defd14a0600578758882acJinsuk Kim    @ServiceThreadOnly
18897fa3a66470d2133796defd14a0600578758882acJinsuk Kim    void processAllDelayedMessages() {
18907fa3a66470d2133796defd14a0600578758882acJinsuk Kim        assertRunOnServiceThread();
18917fa3a66470d2133796defd14a0600578758882acJinsuk Kim        mDelayedMessageBuffer.processAllMessages();
18927fa3a66470d2133796defd14a0600578758882acJinsuk Kim    }
18937fa3a66470d2133796defd14a0600578758882acJinsuk Kim
18947fa3a66470d2133796defd14a0600578758882acJinsuk Kim    @ServiceThreadOnly
18957fa3a66470d2133796defd14a0600578758882acJinsuk Kim    void processDelayedMessages(int address) {
18967fa3a66470d2133796defd14a0600578758882acJinsuk Kim        assertRunOnServiceThread();
18977fa3a66470d2133796defd14a0600578758882acJinsuk Kim        mDelayedMessageBuffer.processMessagesForDevice(address);
18987fa3a66470d2133796defd14a0600578758882acJinsuk Kim    }
18997fa3a66470d2133796defd14a0600578758882acJinsuk Kim
19006e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    @ServiceThreadOnly
19016e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    void processDelayedActiveSource(int address) {
19026e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        assertRunOnServiceThread();
19036e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim        mDelayedMessageBuffer.processActiveSource(address);
19046e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim    }
19056e26f7f7b09dfd8495ec5478a7a4713dab346bc1Jinsuk Kim
1906d4a94db1cd44a536d535de890a0a14919a39a0dcJinsuk Kim    @Override
1907959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo    protected void dump(final IndentingPrintWriter pw) {
1908959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo        super.dump(pw);
1909959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo        pw.println("mArcEstablished: " + mArcEstablished);
1910959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo        pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
1911959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo        pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
1912959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo        pw.println("mSystemAudioMute: " + mSystemAudioMute);
1913959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo        pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
1914959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo        pw.println("mAutoWakeup: " + mAutoWakeup);
1915959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo        pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
1916cb8661c08f4a7b00eaa2ede06a30c32dd3cbc53bJinsuk Kim        pw.println("mPrevPortId: " + mPrevPortId);
19174b4b940c69d22304eb0734ed2432fc70ebadd462Jinsuk Kim        pw.println("CEC devices:");
19184b4b940c69d22304eb0734ed2432fc70ebadd462Jinsuk Kim        pw.increaseIndent();
19194b4b940c69d22304eb0734ed2432fc70ebadd462Jinsuk Kim        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
19204b4b940c69d22304eb0734ed2432fc70ebadd462Jinsuk Kim            pw.println(info);
19214b4b940c69d22304eb0734ed2432fc70ebadd462Jinsuk Kim        }
19224b4b940c69d22304eb0734ed2432fc70ebadd462Jinsuk Kim        pw.decreaseIndent();
1923959d2db12c7c6a06465af1251bc4cece580a72a3Terry Heo    }
19242918e9e133de8066ab497a5f8dac1c310c792767Jinsuk Kim}
1925