Home | History | Annotate | Download | only in hdmi
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.hdmi;
     18 
     19 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE;
     20 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION;
     21 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE;
     22 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED;
     23 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
     24 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN;
     25 import static android.hardware.hdmi.HdmiControlManager.OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT;
     26 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED;
     27 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
     28 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE;
     29 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
     30 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
     31 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
     32 
     33 import android.hardware.hdmi.HdmiControlManager;
     34 import android.hardware.hdmi.HdmiDeviceInfo;
     35 import android.hardware.hdmi.HdmiPortInfo;
     36 import android.hardware.hdmi.HdmiRecordSources;
     37 import android.hardware.hdmi.HdmiTimerRecordSources;
     38 import android.hardware.hdmi.IHdmiControlCallback;
     39 import android.hardware.tv.cec.V1_0.SendMessageResult;
     40 import android.media.AudioManager;
     41 import android.media.AudioSystem;
     42 import android.media.tv.TvInputInfo;
     43 import android.media.tv.TvInputManager.TvInputCallback;
     44 import android.provider.Settings.Global;
     45 import android.util.ArraySet;
     46 import android.util.Slog;
     47 import android.util.SparseArray;
     48 import android.util.SparseBooleanArray;
     49 
     50 import com.android.internal.annotations.GuardedBy;
     51 import com.android.internal.util.IndentingPrintWriter;
     52 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
     53 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
     54 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
     55 
     56 import java.io.UnsupportedEncodingException;
     57 import java.util.ArrayList;
     58 import java.util.Arrays;
     59 import java.util.Collection;
     60 import java.util.Collections;
     61 import java.util.HashMap;
     62 import java.util.Iterator;
     63 import java.util.List;
     64 
     65 /**
     66  * Represent a logical device of type TV residing in Android system.
     67  */
     68 final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     69     private static final String TAG = "HdmiCecLocalDeviceTv";
     70 
     71     // Whether ARC is available or not. "true" means that ARC is established between TV and
     72     // AVR as audio receiver.
     73     @ServiceThreadOnly
     74     private boolean mArcEstablished = false;
     75 
     76     // Stores whether ARC feature is enabled per port.
     77     // True by default for all the ARC-enabled ports.
     78     private final SparseBooleanArray mArcFeatureEnabled = new SparseBooleanArray();
     79 
     80     // Whether the System Audio Control feature is enabled or not. True by default.
     81     @GuardedBy("mLock")
     82     private boolean mSystemAudioControlFeatureEnabled;
     83 
     84     // The previous port id (input) before switching to the new one. This is remembered in order to
     85     // be able to switch to it upon receiving <Inactive Source> from currently active source.
     86     // This remains valid only when the active source was switched via one touch play operation
     87     // (either by TV or source device). Manual port switching invalidates this value to
     88     // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything.
     89     @GuardedBy("mLock")
     90     private int mPrevPortId;
     91 
     92     @GuardedBy("mLock")
     93     private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME;
     94 
     95     @GuardedBy("mLock")
     96     private boolean mSystemAudioMute = false;
     97 
     98     // Copy of mDeviceInfos to guarantee thread-safety.
     99     @GuardedBy("mLock")
    100     private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
    101     // All external cec input(source) devices. Does not include system audio device.
    102     @GuardedBy("mLock")
    103     private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList();
    104 
    105     // Map-like container of all cec devices including local ones.
    106     // device id is used as key of container.
    107     // This is not thread-safe. For external purpose use mSafeDeviceInfos.
    108     private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
    109 
    110     // If true, TV going to standby mode puts other devices also to standby.
    111     private boolean mAutoDeviceOff;
    112 
    113     // If true, TV wakes itself up when receiving <Text/Image View On>.
    114     private boolean mAutoWakeup;
    115 
    116     // List of the logical address of local CEC devices. Unmodifiable, thread-safe.
    117     private List<Integer> mLocalDeviceAddresses;
    118 
    119     private final HdmiCecStandbyModeHandler mStandbyHandler;
    120 
    121     // If true, do not do routing control/send active source for internal source.
    122     // Set to true when the device was woken up by <Text/Image View On>.
    123     private boolean mSkipRoutingControl;
    124 
    125     // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
    126     // other CEC devices since they might not have logical address.
    127     private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
    128 
    129     // Message buffer used to buffer selected messages to process later. <Active Source>
    130     // from a source device, for instance, needs to be buffered if the device is not
    131     // discovered yet. The buffered commands are taken out and when they are ready to
    132     // handle.
    133     private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
    134 
    135     // Defines the callback invoked when TV input framework is updated with input status.
    136     // We are interested in the notification for HDMI input addition event, in order to
    137     // process any CEC commands that arrived before the input is added.
    138     private final TvInputCallback mTvInputCallback = new TvInputCallback() {
    139         @Override
    140         public void onInputAdded(String inputId) {
    141             TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
    142             if (tvInfo == null) return;
    143             HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
    144             if (info == null) return;
    145             addTvInput(inputId, info.getId());
    146             if (info.isCecDevice()) {
    147                 processDelayedActiveSource(info.getLogicalAddress());
    148             }
    149         }
    150 
    151         @Override
    152         public void onInputRemoved(String inputId) {
    153             removeTvInput(inputId);
    154         }
    155     };
    156 
    157     // Keeps the mapping (TV input ID, HDMI device ID) to keep track of the TV inputs ready to
    158     // accept input switching request from HDMI devices. Requests for which the corresponding
    159     // input ID is not yet registered by TV input framework need to be buffered for delayed
    160     // processing.
    161     private final HashMap<String, Integer> mTvInputs = new HashMap<>();
    162 
    163     @ServiceThreadOnly
    164     private void addTvInput(String inputId, int deviceId) {
    165         assertRunOnServiceThread();
    166         mTvInputs.put(inputId, deviceId);
    167     }
    168 
    169     @ServiceThreadOnly
    170     private void removeTvInput(String inputId) {
    171         assertRunOnServiceThread();
    172         mTvInputs.remove(inputId);
    173     }
    174 
    175     @Override
    176     @ServiceThreadOnly
    177     protected boolean isInputReady(int deviceId) {
    178         assertRunOnServiceThread();
    179         return mTvInputs.containsValue(deviceId);
    180     }
    181 
    182     private SelectRequestBuffer mSelectRequestBuffer;
    183 
    184     HdmiCecLocalDeviceTv(HdmiControlService service) {
    185         super(service, HdmiDeviceInfo.DEVICE_TV);
    186         mPrevPortId = Constants.INVALID_PORT_ID;
    187         mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
    188                 true);
    189         mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true);
    190         mSystemAudioControlFeatureEnabled =
    191                 mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);
    192         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
    193     }
    194 
    195     @Override
    196     @ServiceThreadOnly
    197     protected void onAddressAllocated(int logicalAddress, int reason) {
    198         assertRunOnServiceThread();
    199         List<HdmiPortInfo> ports = mService.getPortInfo();
    200         for (HdmiPortInfo port : ports) {
    201             mArcFeatureEnabled.put(port.getId(), port.isArcSupported());
    202         }
    203         mService.registerTvInputCallback(mTvInputCallback);
    204         mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
    205                 mAddress, mService.getPhysicalAddress(), mDeviceType));
    206         mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
    207                 mAddress, mService.getVendorId()));
    208         mCecSwitches.add(mService.getPhysicalAddress());  // TV is a CEC switch too.
    209         mTvInputs.clear();
    210         mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
    211         launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
    212                 reason != HdmiControlService.INITIATED_BY_BOOT_UP);
    213         mLocalDeviceAddresses = initLocalDeviceAddresses();
    214         resetSelectRequestBuffer();
    215         launchDeviceDiscovery();
    216     }
    217 
    218 
    219     @ServiceThreadOnly
    220     private List<Integer> initLocalDeviceAddresses() {
    221         assertRunOnServiceThread();
    222         List<Integer> addresses = new ArrayList<>();
    223         for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
    224             addresses.add(device.getDeviceInfo().getLogicalAddress());
    225         }
    226         return Collections.unmodifiableList(addresses);
    227     }
    228 
    229 
    230     @ServiceThreadOnly
    231     public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) {
    232         assertRunOnServiceThread();
    233         mSelectRequestBuffer = requestBuffer;
    234     }
    235 
    236     @ServiceThreadOnly
    237     private void resetSelectRequestBuffer() {
    238         assertRunOnServiceThread();
    239         setSelectRequestBuffer(SelectRequestBuffer.EMPTY_BUFFER);
    240     }
    241 
    242     @Override
    243     protected int getPreferredAddress() {
    244         return Constants.ADDR_TV;
    245     }
    246 
    247     @Override
    248     protected void setPreferredAddress(int addr) {
    249         Slog.w(TAG, "Preferred addres will not be stored for TV");
    250     }
    251 
    252     @Override
    253     @ServiceThreadOnly
    254     boolean dispatchMessage(HdmiCecMessage message) {
    255         assertRunOnServiceThread();
    256         if (mService.isPowerStandby() && !mService.isWakeUpMessageReceived()
    257                 && mStandbyHandler.handleCommand(message)) {
    258             return true;
    259         }
    260         return super.onMessage(message);
    261     }
    262 
    263     /**
    264      * Performs the action 'device select', or 'one touch play' initiated by TV.
    265      *
    266      * @param id id of HDMI device to select
    267      * @param callback callback object to report the result with
    268      */
    269     @ServiceThreadOnly
    270     void deviceSelect(int id, IHdmiControlCallback callback) {
    271         assertRunOnServiceThread();
    272         HdmiDeviceInfo targetDevice = mDeviceInfos.get(id);
    273         if (targetDevice == null) {
    274             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
    275             return;
    276         }
    277         int targetAddress = targetDevice.getLogicalAddress();
    278         ActiveSource active = getActiveSource();
    279         if (targetDevice.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON
    280                 && active.isValid()
    281                 && targetAddress == active.logicalAddress) {
    282             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
    283             return;
    284         }
    285         if (targetAddress == Constants.ADDR_INTERNAL) {
    286             handleSelectInternalSource();
    287             // Switching to internal source is always successful even when CEC control is disabled.
    288             setActiveSource(targetAddress, mService.getPhysicalAddress());
    289             setActivePath(mService.getPhysicalAddress());
    290             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
    291             return;
    292         }
    293         if (!mService.isControlEnabled()) {
    294             setActiveSource(targetDevice);
    295             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
    296             return;
    297         }
    298         removeAction(DeviceSelectAction.class);
    299         addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
    300     }
    301 
    302     @ServiceThreadOnly
    303     private void handleSelectInternalSource() {
    304         assertRunOnServiceThread();
    305         // Seq #18
    306         if (mService.isControlEnabled() && getActiveSource().logicalAddress != mAddress) {
    307             updateActiveSource(mAddress, mService.getPhysicalAddress());
    308             if (mSkipRoutingControl) {
    309                 mSkipRoutingControl = false;
    310                 return;
    311             }
    312             HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
    313                     mAddress, mService.getPhysicalAddress());
    314             mService.sendCecCommand(activeSource);
    315         }
    316     }
    317 
    318     @ServiceThreadOnly
    319     void updateActiveSource(int logicalAddress, int physicalAddress) {
    320         assertRunOnServiceThread();
    321         updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress));
    322     }
    323 
    324     @ServiceThreadOnly
    325     void updateActiveSource(ActiveSource newActive) {
    326         assertRunOnServiceThread();
    327         // Seq #14
    328         if (getActiveSource().equals(newActive)) {
    329             return;
    330         }
    331         setActiveSource(newActive);
    332         int logicalAddress = newActive.logicalAddress;
    333         if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) {
    334             if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
    335                 setPrevPortId(getActivePortId());
    336             }
    337             // TODO: Show the OSD banner related to the new active source device.
    338         } else {
    339             // TODO: If displayed, remove the OSD banner related to the previous
    340             //       active source device.
    341         }
    342     }
    343 
    344     /**
    345      * Returns the previous port id kept to handle input switching on <Inactive Source>.
    346      */
    347     int getPrevPortId() {
    348         synchronized (mLock) {
    349             return mPrevPortId;
    350         }
    351     }
    352 
    353     /**
    354      * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
    355      * taken for <Inactive Source>.
    356      */
    357     void setPrevPortId(int portId) {
    358         synchronized (mLock) {
    359             mPrevPortId = portId;
    360         }
    361     }
    362 
    363     @ServiceThreadOnly
    364     void updateActiveInput(int path, boolean notifyInputChange) {
    365         assertRunOnServiceThread();
    366         // Seq #15
    367         setActivePath(path);
    368         // TODO: Handle PAP/PIP case.
    369         // Show OSD port change banner
    370         if (notifyInputChange) {
    371             ActiveSource activeSource = getActiveSource();
    372             HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress);
    373             if (info == null) {
    374                 info = mService.getDeviceInfoByPort(getActivePortId());
    375                 if (info == null) {
    376                     // No CEC/MHL device is present at the port. Attempt to switch to
    377                     // the hardware port itself for non-CEC devices that may be connected.
    378                     info = new HdmiDeviceInfo(path, getActivePortId());
    379                 }
    380             }
    381             mService.invokeInputChangeListener(info);
    382         }
    383     }
    384 
    385     @ServiceThreadOnly
    386     void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
    387         assertRunOnServiceThread();
    388         // Seq #20
    389         if (!mService.isValidPortId(portId)) {
    390             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
    391             return;
    392         }
    393         if (portId == getActivePortId()) {
    394             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
    395             return;
    396         }
    397         getActiveSource().invalidate();
    398         if (!mService.isControlEnabled()) {
    399             setActivePortId(portId);
    400             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
    401             return;
    402         }
    403         int oldPath = getActivePortId() != Constants.INVALID_PORT_ID
    404                 ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress();
    405         setActivePath(oldPath);
    406         if (mSkipRoutingControl) {
    407             mSkipRoutingControl = false;
    408             return;
    409         }
    410         int newPath = mService.portIdToPath(portId);
    411         startRoutingControl(oldPath, newPath, true, callback);
    412     }
    413 
    414     @ServiceThreadOnly
    415     void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus,
    416             IHdmiControlCallback callback) {
    417         assertRunOnServiceThread();
    418         if (oldPath == newPath) {
    419             return;
    420         }
    421         HdmiCecMessage routingChange =
    422                 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
    423         mService.sendCecCommand(routingChange);
    424         removeAction(RoutingControlAction.class);
    425         addAndStartAction(
    426                 new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback));
    427     }
    428 
    429     @ServiceThreadOnly
    430     int getPowerStatus() {
    431         assertRunOnServiceThread();
    432         return mService.getPowerStatus();
    433     }
    434 
    435     @Override
    436     protected int findKeyReceiverAddress() {
    437         if (getActiveSource().isValid()) {
    438             return getActiveSource().logicalAddress;
    439         }
    440         HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath());
    441         if (info != null) {
    442             return info.getLogicalAddress();
    443         }
    444         return Constants.ADDR_INVALID;
    445     }
    446 
    447     @Override
    448     @ServiceThreadOnly
    449     protected boolean handleActiveSource(HdmiCecMessage message) {
    450         assertRunOnServiceThread();
    451         int logicalAddress = message.getSource();
    452         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
    453         HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
    454         if (info == null) {
    455             if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
    456                 HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
    457                 mDelayedMessageBuffer.add(message);
    458             }
    459         } else if (isInputReady(info.getId())
    460                 || info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
    461             updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
    462             ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
    463             ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
    464         } else {
    465             HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
    466             mDelayedMessageBuffer.add(message);
    467         }
    468         return true;
    469     }
    470 
    471     @Override
    472     @ServiceThreadOnly
    473     protected boolean handleInactiveSource(HdmiCecMessage message) {
    474         assertRunOnServiceThread();
    475         // Seq #10
    476 
    477         // Ignore <Inactive Source> from non-active source device.
    478         if (getActiveSource().logicalAddress != message.getSource()) {
    479             return true;
    480         }
    481         if (isProhibitMode()) {
    482             return true;
    483         }
    484         int portId = getPrevPortId();
    485         if (portId != Constants.INVALID_PORT_ID) {
    486             // TODO: Do this only if TV is not showing multiview like PIP/PAP.
    487 
    488             HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource());
    489             if (inactiveSource == null) {
    490                 return true;
    491             }
    492             if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
    493                 return true;
    494             }
    495             // TODO: Switch the TV freeze mode off
    496 
    497             doManualPortSwitching(portId, null);
    498             setPrevPortId(Constants.INVALID_PORT_ID);
    499         } else {
    500             // No HDMI port to switch to was found. Notify the input change listers to
    501             // switch to the lastly shown internal input.
    502             getActiveSource().invalidate();
    503             setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
    504             mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
    505         }
    506         return true;
    507     }
    508 
    509     @Override
    510     @ServiceThreadOnly
    511     protected boolean handleRequestActiveSource(HdmiCecMessage message) {
    512         assertRunOnServiceThread();
    513         // Seq #19
    514         if (mAddress == getActiveSource().logicalAddress) {
    515             mService.sendCecCommand(
    516                     HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
    517         }
    518         return true;
    519     }
    520 
    521     @Override
    522     @ServiceThreadOnly
    523     protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
    524         assertRunOnServiceThread();
    525         if (!broadcastMenuLanguage(mService.getLanguage())) {
    526             Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
    527         }
    528         return true;
    529     }
    530 
    531     @ServiceThreadOnly
    532     boolean broadcastMenuLanguage(String language) {
    533         assertRunOnServiceThread();
    534         HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
    535                 mAddress, language);
    536         if (command != null) {
    537             mService.sendCecCommand(command);
    538             return true;
    539         }
    540         return false;
    541     }
    542 
    543     @Override
    544     @ServiceThreadOnly
    545     protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
    546         assertRunOnServiceThread();
    547         int path = HdmiUtils.twoBytesToInt(message.getParams());
    548         int address = message.getSource();
    549         int type = message.getParams()[2];
    550 
    551         if (updateCecSwitchInfo(address, type, path)) return true;
    552 
    553         // Ignore if [Device Discovery Action] is going on.
    554         if (hasAction(DeviceDiscoveryAction.class)) {
    555             Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
    556             return true;
    557         }
    558 
    559         if (!isInDeviceList(address, path)) {
    560             handleNewDeviceAtTheTailOfActivePath(path);
    561         }
    562 
    563         // Add the device ahead with default information to handle <Active Source>
    564         // promptly, rather than waiting till the new device action is finished.
    565         HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type,
    566                 Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address));
    567         addCecDevice(deviceInfo);
    568         startNewDeviceAction(ActiveSource.of(address, path), type);
    569         return true;
    570     }
    571 
    572     @Override
    573     protected boolean handleReportPowerStatus(HdmiCecMessage command) {
    574         int newStatus = command.getParams()[0] & 0xFF;
    575         updateDevicePowerStatus(command.getSource(), newStatus);
    576         return true;
    577     }
    578 
    579     @Override
    580     protected boolean handleTimerStatus(HdmiCecMessage message) {
    581         // Do nothing.
    582         return true;
    583     }
    584 
    585     @Override
    586     protected boolean handleRecordStatus(HdmiCecMessage message) {
    587         // Do nothing.
    588         return true;
    589     }
    590 
    591     boolean updateCecSwitchInfo(int address, int type, int path) {
    592         if (address == Constants.ADDR_UNREGISTERED
    593                 && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
    594             mCecSwitches.add(path);
    595             updateSafeDeviceInfoList();
    596             return true;  // Pure switch does not need further processing. Return here.
    597         }
    598         if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
    599             mCecSwitches.add(path);
    600         }
    601         return false;
    602     }
    603 
    604     void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
    605         for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
    606             // If there is new device action which has the same logical address and path
    607             // ignore new request.
    608             // NewDeviceAction is created whenever it receives <Report Physical Address>.
    609             // And there is a chance starting NewDeviceAction for the same source.
    610             // Usually, new device sends <Report Physical Address> when it's plugged
    611             // in. However, TV can detect a new device from HotPlugDetectionAction,
    612             // which sends <Give Physical Address> to the source for newly detected
    613             // device.
    614             if (action.isActionOf(activeSource)) {
    615                 return;
    616             }
    617         }
    618 
    619         addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
    620                 activeSource.physicalAddress, deviceType));
    621     }
    622 
    623     private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
    624         // Seq #22
    625         if (isTailOfActivePath(path, getActivePath())) {
    626             int newPath = mService.portIdToPath(getActivePortId());
    627             setActivePath(newPath);
    628             startRoutingControl(getActivePath(), newPath, false, null);
    629             return true;
    630         }
    631         return false;
    632     }
    633 
    634     /**
    635      * Whether the given path is located in the tail of current active path.
    636      *
    637      * @param path to be tested
    638      * @param activePath current active path
    639      * @return true if the given path is located in the tail of current active path; otherwise,
    640      *         false
    641      */
    642     static boolean isTailOfActivePath(int path, int activePath) {
    643         // If active routing path is internal source, return false.
    644         if (activePath == 0) {
    645             return false;
    646         }
    647         for (int i = 12; i >= 0; i -= 4) {
    648             int curActivePath = (activePath >> i) & 0xF;
    649             if (curActivePath == 0) {
    650                 return true;
    651             } else {
    652                 int curPath = (path >> i) & 0xF;
    653                 if (curPath != curActivePath) {
    654                     return false;
    655                 }
    656             }
    657         }
    658         return false;
    659     }
    660 
    661     @Override
    662     @ServiceThreadOnly
    663     protected boolean handleRoutingChange(HdmiCecMessage message) {
    664         assertRunOnServiceThread();
    665         // Seq #21
    666         byte[] params = message.getParams();
    667         int currentPath = HdmiUtils.twoBytesToInt(params);
    668         if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
    669             getActiveSource().invalidate();
    670             removeAction(RoutingControlAction.class);
    671             int newPath = HdmiUtils.twoBytesToInt(params, 2);
    672             addAndStartAction(new RoutingControlAction(this, newPath, true, null));
    673         }
    674         return true;
    675     }
    676 
    677     @Override
    678     @ServiceThreadOnly
    679     protected boolean handleReportAudioStatus(HdmiCecMessage message) {
    680         assertRunOnServiceThread();
    681 
    682         boolean mute = HdmiUtils.isAudioStatusMute(message);
    683         int volume = HdmiUtils.getAudioStatusVolume(message);
    684         setAudioStatus(mute, volume);
    685         return true;
    686     }
    687 
    688     @Override
    689     @ServiceThreadOnly
    690     protected boolean handleTextViewOn(HdmiCecMessage message) {
    691         assertRunOnServiceThread();
    692 
    693         // Note that <Text View On> (and <Image View On>) command won't be handled here in
    694         // most cases. A dedicated microcontroller should be in charge while Android system
    695         // is in sleep mode, and the command need not be passed up to this service.
    696         // The only situation where the command reaches this handler is that sleep mode is
    697         // implemented in such a way that Android system is not really put to standby mode
    698         // but only the display is set to blank. Then the command leads to the effect of
    699         // turning on the display by the invocation of PowerManager.wakeUp().
    700         if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
    701             mService.wakeUp();
    702         }
    703         return true;
    704     }
    705 
    706     @Override
    707     @ServiceThreadOnly
    708     protected boolean handleImageViewOn(HdmiCecMessage message) {
    709         assertRunOnServiceThread();
    710         // Currently, it's the same as <Text View On>.
    711         return handleTextViewOn(message);
    712     }
    713 
    714     @Override
    715     @ServiceThreadOnly
    716     protected boolean handleSetOsdName(HdmiCecMessage message) {
    717         int source = message.getSource();
    718         HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
    719         // If the device is not in device list, ignore it.
    720         if (deviceInfo == null) {
    721             Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
    722             return true;
    723         }
    724         String osdName = null;
    725         try {
    726             osdName = new String(message.getParams(), "US-ASCII");
    727         } catch (UnsupportedEncodingException e) {
    728             Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
    729             return true;
    730         }
    731 
    732         if (deviceInfo.getDisplayName().equals(osdName)) {
    733             Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
    734             return true;
    735         }
    736 
    737         addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
    738                 deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
    739                 deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
    740         return true;
    741     }
    742 
    743     @ServiceThreadOnly
    744     private void launchDeviceDiscovery() {
    745         assertRunOnServiceThread();
    746         clearDeviceInfoList();
    747         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
    748                 new DeviceDiscoveryCallback() {
    749                     @Override
    750                     public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
    751                         for (HdmiDeviceInfo info : deviceInfos) {
    752                             addCecDevice(info);
    753                         }
    754 
    755                         // Since we removed all devices when it's start and
    756                         // device discovery action does not poll local devices,
    757                         // we should put device info of local device manually here
    758                         for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
    759                             addCecDevice(device.getDeviceInfo());
    760                         }
    761 
    762                         mSelectRequestBuffer.process();
    763                         resetSelectRequestBuffer();
    764 
    765                         addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
    766                         addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
    767 
    768                         HdmiDeviceInfo avr = getAvrDeviceInfo();
    769                         if (avr != null) {
    770                             onNewAvrAdded(avr);
    771                         } else {
    772                             setSystemAudioMode(false);
    773                         }
    774                     }
    775                 });
    776         addAndStartAction(action);
    777     }
    778 
    779     @ServiceThreadOnly
    780     void onNewAvrAdded(HdmiDeviceInfo avr) {
    781         assertRunOnServiceThread();
    782         addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
    783         if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId())
    784                 && !hasAction(SetArcTransmissionStateAction.class)) {
    785             startArcAction(true);
    786         }
    787     }
    788 
    789     // Clear all device info.
    790     @ServiceThreadOnly
    791     private void clearDeviceInfoList() {
    792         assertRunOnServiceThread();
    793         for (HdmiDeviceInfo info : mSafeExternalInputs) {
    794             invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
    795         }
    796         mDeviceInfos.clear();
    797         updateSafeDeviceInfoList();
    798     }
    799 
    800     @ServiceThreadOnly
    801     // Seq #32
    802     void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
    803         assertRunOnServiceThread();
    804         if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
    805             setSystemAudioMode(false);
    806             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
    807             return;
    808         }
    809         HdmiDeviceInfo avr = getAvrDeviceInfo();
    810         if (avr == null) {
    811             setSystemAudioMode(false);
    812             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
    813             return;
    814         }
    815 
    816         addAndStartAction(
    817                 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
    818     }
    819 
    820     // # Seq 25
    821     void setSystemAudioMode(boolean on) {
    822         if (!isSystemAudioControlFeatureEnabled() && on) {
    823             HdmiLogger.debug("Cannot turn on system audio mode "
    824                     + "because the System Audio Control feature is disabled.");
    825             return;
    826         }
    827         HdmiLogger.debug("System Audio Mode change[old:%b new:%b]",
    828                 mService.isSystemAudioActivated(), on);
    829         updateAudioManagerForSystemAudio(on);
    830         synchronized (mLock) {
    831             if (mService.isSystemAudioActivated() != on) {
    832                 mService.setSystemAudioActivated(on);
    833                 mService.announceSystemAudioModeChange(on);
    834             }
    835             startArcAction(on);
    836         }
    837     }
    838 
    839     private void updateAudioManagerForSystemAudio(boolean on) {
    840         int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
    841         HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
    842     }
    843 
    844     boolean isSystemAudioActivated() {
    845         if (!hasSystemAudioDevice()) {
    846             return false;
    847         }
    848         return mService.isSystemAudioActivated();
    849     }
    850 
    851     @ServiceThreadOnly
    852     void setSystemAudioControlFeatureEnabled(boolean enabled) {
    853         assertRunOnServiceThread();
    854         synchronized (mLock) {
    855             mSystemAudioControlFeatureEnabled = enabled;
    856         }
    857         if (hasSystemAudioDevice()) {
    858             changeSystemAudioMode(enabled, null);
    859         }
    860     }
    861 
    862     boolean isSystemAudioControlFeatureEnabled() {
    863         synchronized (mLock) {
    864             return mSystemAudioControlFeatureEnabled;
    865         }
    866     }
    867 
    868     /**
    869      * Change ARC status into the given {@code enabled} status.
    870      *
    871      * @return {@code true} if ARC was in "Enabled" status
    872      */
    873     @ServiceThreadOnly
    874     boolean setArcStatus(boolean enabled) {
    875         assertRunOnServiceThread();
    876 
    877         HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
    878         boolean oldStatus = mArcEstablished;
    879         // 1. Enable/disable ARC circuit.
    880         enableAudioReturnChannel(enabled);
    881         // 2. Notify arc status to audio service.
    882         notifyArcStatusToAudioService(enabled);
    883         // 3. Update arc status;
    884         mArcEstablished = enabled;
    885         return oldStatus;
    886     }
    887 
    888     /**
    889      * Switch hardware ARC circuit in the system.
    890      */
    891     @ServiceThreadOnly
    892     void enableAudioReturnChannel(boolean enabled) {
    893         assertRunOnServiceThread();
    894         HdmiDeviceInfo avr = getAvrDeviceInfo();
    895         if (avr != null) {
    896             mService.enableAudioReturnChannel(avr.getPortId(), enabled);
    897         }
    898     }
    899 
    900     @ServiceThreadOnly
    901     boolean isConnected(int portId) {
    902         assertRunOnServiceThread();
    903         return mService.isConnected(portId);
    904     }
    905 
    906     private void notifyArcStatusToAudioService(boolean enabled) {
    907         // Note that we don't set any name to ARC.
    908         mService.getAudioManager().setWiredDeviceConnectionState(
    909                 AudioSystem.DEVICE_OUT_HDMI_ARC,
    910                 enabled ? 1 : 0, "", "");
    911     }
    912 
    913     /**
    914      * Returns true if ARC is currently established on a certain port.
    915      */
    916     @ServiceThreadOnly
    917     boolean isArcEstablished() {
    918         assertRunOnServiceThread();
    919         if (mArcEstablished) {
    920             for (int i = 0; i < mArcFeatureEnabled.size(); i++) {
    921                 if (mArcFeatureEnabled.valueAt(i)) return true;
    922             }
    923         }
    924         return false;
    925     }
    926 
    927     @ServiceThreadOnly
    928     void changeArcFeatureEnabled(int portId, boolean enabled) {
    929         assertRunOnServiceThread();
    930         if (mArcFeatureEnabled.get(portId) == enabled) {
    931             return;
    932         }
    933         mArcFeatureEnabled.put(portId, enabled);
    934         HdmiDeviceInfo avr = getAvrDeviceInfo();
    935         if (avr == null || avr.getPortId() != portId) {
    936             return;
    937         }
    938         if (enabled && !mArcEstablished) {
    939             startArcAction(true);
    940         } else if (!enabled && mArcEstablished) {
    941             startArcAction(false);
    942         }
    943     }
    944 
    945     @ServiceThreadOnly
    946     boolean isArcFeatureEnabled(int portId) {
    947         assertRunOnServiceThread();
    948         return mArcFeatureEnabled.get(portId);
    949     }
    950 
    951     @ServiceThreadOnly
    952     void startArcAction(boolean enabled) {
    953         assertRunOnServiceThread();
    954         HdmiDeviceInfo info = getAvrDeviceInfo();
    955         if (info == null) {
    956             Slog.w(TAG, "Failed to start arc action; No AVR device.");
    957             return;
    958         }
    959         if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
    960             Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
    961             if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
    962                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
    963             }
    964             return;
    965         }
    966 
    967         // Terminate opposite action and start action if not exist.
    968         if (enabled) {
    969             removeAction(RequestArcTerminationAction.class);
    970             if (!hasAction(RequestArcInitiationAction.class)) {
    971                 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
    972             }
    973         } else {
    974             removeAction(RequestArcInitiationAction.class);
    975             if (!hasAction(RequestArcTerminationAction.class)) {
    976                 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
    977             }
    978         }
    979     }
    980 
    981     private boolean isDirectConnectAddress(int physicalAddress) {
    982         return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
    983     }
    984 
    985     void setAudioStatus(boolean mute, int volume) {
    986         if (!isSystemAudioActivated()) {
    987             return;
    988         }
    989         synchronized (mLock) {
    990             mSystemAudioMute = mute;
    991             mSystemAudioVolume = volume;
    992             int maxVolume = mService.getAudioManager().getStreamMaxVolume(
    993                     AudioManager.STREAM_MUSIC);
    994             mService.setAudioStatus(mute,
    995                     VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
    996             displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
    997                     mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
    998         }
    999     }
   1000 
   1001     @ServiceThreadOnly
   1002     void changeVolume(int curVolume, int delta, int maxVolume) {
   1003         assertRunOnServiceThread();
   1004         if (getAvrDeviceInfo() == null) {
   1005             // On initialization process, getAvrDeviceInfo() may return null and cause exception
   1006             return;
   1007         }
   1008         if (delta == 0 || !isSystemAudioActivated()) {
   1009             return;
   1010         }
   1011 
   1012         int targetVolume = curVolume + delta;
   1013         int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
   1014         synchronized (mLock) {
   1015             // If new volume is the same as current system audio volume, just ignore it.
   1016             // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
   1017             if (cecVolume == mSystemAudioVolume) {
   1018                 // Update tv volume with system volume value.
   1019                 mService.setAudioStatus(false,
   1020                         VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
   1021                 return;
   1022             }
   1023         }
   1024 
   1025         List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
   1026         if (actions.isEmpty()) {
   1027             addAndStartAction(new VolumeControlAction(this,
   1028                     getAvrDeviceInfo().getLogicalAddress(), delta > 0));
   1029         } else {
   1030             actions.get(0).handleVolumeChange(delta > 0);
   1031         }
   1032     }
   1033 
   1034     @ServiceThreadOnly
   1035     void changeMute(boolean mute) {
   1036         assertRunOnServiceThread();
   1037         if (getAvrDeviceInfo() == null) {
   1038             // On initialization process, getAvrDeviceInfo() may return null and cause exception
   1039             return;
   1040         }
   1041         HdmiLogger.debug("[A]:Change mute:%b", mute);
   1042         synchronized (mLock) {
   1043             if (mSystemAudioMute == mute) {
   1044                 HdmiLogger.debug("No need to change mute.");
   1045                 return;
   1046             }
   1047         }
   1048         if (!isSystemAudioActivated()) {
   1049             HdmiLogger.debug("[A]:System audio is not activated.");
   1050             return;
   1051         }
   1052 
   1053         // Remove existing volume action.
   1054         removeAction(VolumeControlAction.class);
   1055         sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
   1056                 HdmiCecKeycode.getMuteKey(mute));
   1057     }
   1058 
   1059     @Override
   1060     @ServiceThreadOnly
   1061     protected boolean handleInitiateArc(HdmiCecMessage message) {
   1062         assertRunOnServiceThread();
   1063 
   1064         if (!canStartArcUpdateAction(message.getSource(), true)) {
   1065             HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo();
   1066             if (avrDeviceInfo == null) {
   1067                 // AVR may not have been discovered yet. Delay the message processing.
   1068                 mDelayedMessageBuffer.add(message);
   1069                 return true;
   1070             }
   1071             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
   1072             if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) {
   1073                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
   1074             }
   1075             return true;
   1076         }
   1077 
   1078         // In case where <Initiate Arc> is started by <Request ARC Initiation>
   1079         // need to clean up RequestArcInitiationAction.
   1080         removeAction(RequestArcInitiationAction.class);
   1081         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
   1082                 message.getSource(), true);
   1083         addAndStartAction(action);
   1084         return true;
   1085     }
   1086 
   1087     private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) {
   1088         HdmiDeviceInfo avr = getAvrDeviceInfo();
   1089         if (avr != null
   1090                 && (avrAddress == avr.getLogicalAddress())
   1091                 && isConnectedToArcPort(avr.getPhysicalAddress())
   1092                 && isDirectConnectAddress(avr.getPhysicalAddress())) {
   1093             if (enabled) {
   1094                 return isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId());
   1095             } else {
   1096                 return true;
   1097             }
   1098         } else {
   1099             return false;
   1100         }
   1101     }
   1102 
   1103     @Override
   1104     @ServiceThreadOnly
   1105     protected boolean handleTerminateArc(HdmiCecMessage message) {
   1106         assertRunOnServiceThread();
   1107         if (mService .isPowerStandbyOrTransient()) {
   1108             setArcStatus(false);
   1109             return true;
   1110         }
   1111         // Do not check ARC configuration since the AVR might have been already removed.
   1112         // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by
   1113         // <Request ARC Termination>.
   1114         removeAction(RequestArcTerminationAction.class);
   1115         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
   1116                 message.getSource(), false);
   1117         addAndStartAction(action);
   1118         return true;
   1119     }
   1120 
   1121     @Override
   1122     @ServiceThreadOnly
   1123     protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
   1124         assertRunOnServiceThread();
   1125         boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message);
   1126         if (!isMessageForSystemAudio(message)) {
   1127             if (getAvrDeviceInfo() == null) {
   1128                 // AVR may not have been discovered yet. Delay the message processing.
   1129                 mDelayedMessageBuffer.add(message);
   1130             } else {
   1131                 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
   1132                 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
   1133             }
   1134             return true;
   1135         } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) {
   1136             HdmiLogger.debug("Ignoring <Set System Audio Mode> message "
   1137                     + "because the System Audio Control feature is disabled: %s", message);
   1138             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
   1139             return true;
   1140         }
   1141         removeAction(SystemAudioAutoInitiationAction.class);
   1142         SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
   1143                 message.getSource(), systemAudioStatus, null);
   1144         addAndStartAction(action);
   1145         return true;
   1146     }
   1147 
   1148     @Override
   1149     @ServiceThreadOnly
   1150     protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
   1151         assertRunOnServiceThread();
   1152         if (!isMessageForSystemAudio(message)) {
   1153             HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
   1154             // Ignore this message.
   1155             return true;
   1156         }
   1157         setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message));
   1158         return true;
   1159     }
   1160 
   1161     // Seq #53
   1162     @Override
   1163     @ServiceThreadOnly
   1164     protected boolean handleRecordTvScreen(HdmiCecMessage message) {
   1165         List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
   1166         if (!actions.isEmpty()) {
   1167             // Assumes only one OneTouchRecordAction.
   1168             OneTouchRecordAction action = actions.get(0);
   1169             if (action.getRecorderAddress() != message.getSource()) {
   1170                 announceOneTouchRecordResult(
   1171                         message.getSource(),
   1172                         HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
   1173             }
   1174             return super.handleRecordTvScreen(message);
   1175         }
   1176 
   1177         int recorderAddress = message.getSource();
   1178         byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
   1179         int reason = startOneTouchRecord(recorderAddress, recordSource);
   1180         if (reason != Constants.ABORT_NO_ERROR) {
   1181             mService.maySendFeatureAbortCommand(message, reason);
   1182         }
   1183         return true;
   1184     }
   1185 
   1186     @Override
   1187     protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
   1188         byte[] params = message.getParams();
   1189         int timerClearedStatusData = params[0] & 0xFF;
   1190         announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
   1191         return true;
   1192     }
   1193 
   1194     void announceOneTouchRecordResult(int recorderAddress, int result) {
   1195         mService.invokeOneTouchRecordResult(recorderAddress, result);
   1196     }
   1197 
   1198     void announceTimerRecordingResult(int recorderAddress, int result) {
   1199         mService.invokeTimerRecordingResult(recorderAddress, result);
   1200     }
   1201 
   1202     void announceClearTimerRecordingResult(int recorderAddress, int result) {
   1203         mService.invokeClearTimerRecordingResult(recorderAddress, result);
   1204     }
   1205 
   1206     private boolean isMessageForSystemAudio(HdmiCecMessage message) {
   1207         return mService.isControlEnabled()
   1208                 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
   1209                 && (message.getDestination() == Constants.ADDR_TV
   1210                         || message.getDestination() == Constants.ADDR_BROADCAST)
   1211                 && getAvrDeviceInfo() != null;
   1212     }
   1213 
   1214     /**
   1215      * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
   1216      * logical address as new device info's.
   1217      *
   1218      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
   1219      *
   1220      * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
   1221      * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
   1222      *         that has the same logical address as new one has.
   1223      */
   1224     @ServiceThreadOnly
   1225     private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
   1226         assertRunOnServiceThread();
   1227         HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
   1228         if (oldDeviceInfo != null) {
   1229             removeDeviceInfo(deviceInfo.getId());
   1230         }
   1231         mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
   1232         updateSafeDeviceInfoList();
   1233         return oldDeviceInfo;
   1234     }
   1235 
   1236     /**
   1237      * Remove a device info corresponding to the given {@code logicalAddress}.
   1238      * It returns removed {@link HdmiDeviceInfo} if exists.
   1239      *
   1240      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
   1241      *
   1242      * @param id id of device to be removed
   1243      * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
   1244      */
   1245     @ServiceThreadOnly
   1246     private HdmiDeviceInfo removeDeviceInfo(int id) {
   1247         assertRunOnServiceThread();
   1248         HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
   1249         if (deviceInfo != null) {
   1250             mDeviceInfos.remove(id);
   1251         }
   1252         updateSafeDeviceInfoList();
   1253         return deviceInfo;
   1254     }
   1255 
   1256     /**
   1257      * Return a list of all {@link HdmiDeviceInfo}.
   1258      *
   1259      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
   1260      * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
   1261      * does not include local device.
   1262      */
   1263     @ServiceThreadOnly
   1264     List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
   1265         assertRunOnServiceThread();
   1266         if (includeLocalDevice) {
   1267             return HdmiUtils.sparseArrayToList(mDeviceInfos);
   1268         } else {
   1269             ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
   1270             for (int i = 0; i < mDeviceInfos.size(); ++i) {
   1271                 HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
   1272                 if (!isLocalDeviceAddress(info.getLogicalAddress())) {
   1273                     infoList.add(info);
   1274                 }
   1275             }
   1276             return infoList;
   1277         }
   1278     }
   1279 
   1280     /**
   1281      * Return external input devices.
   1282      */
   1283     @GuardedBy("mLock")
   1284     List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
   1285         return mSafeExternalInputs;
   1286     }
   1287 
   1288     @ServiceThreadOnly
   1289     private void updateSafeDeviceInfoList() {
   1290         assertRunOnServiceThread();
   1291         List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
   1292         List<HdmiDeviceInfo> externalInputs = getInputDevices();
   1293         synchronized (mLock) {
   1294             mSafeAllDeviceInfos = copiedDevices;
   1295             mSafeExternalInputs = externalInputs;
   1296         }
   1297     }
   1298 
   1299     /**
   1300      * Return a list of external cec input (source) devices.
   1301      *
   1302      * <p>Note that this effectively excludes non-source devices like system audio,
   1303      * secondary TV.
   1304      */
   1305     private List<HdmiDeviceInfo> getInputDevices() {
   1306         ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
   1307         for (int i = 0; i < mDeviceInfos.size(); ++i) {
   1308             HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
   1309             if (isLocalDeviceAddress(info.getLogicalAddress())) {
   1310                 continue;
   1311             }
   1312             if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
   1313                 infoList.add(info);
   1314             }
   1315         }
   1316         return infoList;
   1317     }
   1318 
   1319     // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
   1320     // Returns true if the policy is set to true, and the device to check does not have
   1321     // a parent CEC device (which should be the CEC-enabled switch) in the list.
   1322     private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
   1323         return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
   1324                 && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
   1325     }
   1326 
   1327     private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
   1328         for (int switchPath : switches) {
   1329             if (isParentPath(switchPath, path)) {
   1330                 return true;
   1331             }
   1332         }
   1333         return false;
   1334     }
   1335 
   1336     private static boolean isParentPath(int parentPath, int childPath) {
   1337         // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
   1338         // If child's last non-zero nibble is removed, the result equals to the parent.
   1339         for (int i = 0; i <= 12; i += 4) {
   1340             int nibble = (childPath >> i) & 0xF;
   1341             if (nibble != 0) {
   1342                 int parentNibble = (parentPath >> i) & 0xF;
   1343                 return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
   1344             }
   1345         }
   1346         return false;
   1347     }
   1348 
   1349     private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
   1350         if (!hideDevicesBehindLegacySwitch(info)) {
   1351             mService.invokeDeviceEventListeners(info, status);
   1352         }
   1353     }
   1354 
   1355     private boolean isLocalDeviceAddress(int address) {
   1356         return mLocalDeviceAddresses.contains(address);
   1357     }
   1358 
   1359     @ServiceThreadOnly
   1360     HdmiDeviceInfo getAvrDeviceInfo() {
   1361         assertRunOnServiceThread();
   1362         return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
   1363     }
   1364 
   1365     /**
   1366      * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
   1367      *
   1368      * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
   1369      *
   1370      * @param logicalAddress logical address of the device to be retrieved
   1371      * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
   1372      *         Returns null if no logical address matched
   1373      */
   1374     @ServiceThreadOnly
   1375     HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
   1376         assertRunOnServiceThread();
   1377         return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
   1378     }
   1379 
   1380     boolean hasSystemAudioDevice() {
   1381         return getSafeAvrDeviceInfo() != null;
   1382     }
   1383 
   1384     HdmiDeviceInfo getSafeAvrDeviceInfo() {
   1385         return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
   1386     }
   1387 
   1388     /**
   1389      * Thread safe version of {@link #getCecDeviceInfo(int)}.
   1390      *
   1391      * @param logicalAddress logical address to be retrieved
   1392      * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
   1393      *         Returns null if no logical address matched
   1394      */
   1395     HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
   1396         synchronized (mLock) {
   1397             for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
   1398                 if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
   1399                     return info;
   1400                 }
   1401             }
   1402             return null;
   1403         }
   1404     }
   1405 
   1406     @GuardedBy("mLock")
   1407     List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
   1408         ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
   1409         for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
   1410             if (isLocalDeviceAddress(info.getLogicalAddress())) {
   1411                 continue;
   1412             }
   1413             infoList.add(info);
   1414         }
   1415         return infoList;
   1416     }
   1417 
   1418     /**
   1419      * Called when a device is newly added or a new device is detected or
   1420      * existing device is updated.
   1421      *
   1422      * @param info device info of a new device.
   1423      */
   1424     @ServiceThreadOnly
   1425     final void addCecDevice(HdmiDeviceInfo info) {
   1426         assertRunOnServiceThread();
   1427         HdmiDeviceInfo old = addDeviceInfo(info);
   1428         if (info.getLogicalAddress() == mAddress) {
   1429             // The addition of TV device itself should not be notified.
   1430             return;
   1431         }
   1432         if (old == null) {
   1433             invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
   1434         } else if (!old.equals(info)) {
   1435             invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
   1436             invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
   1437         }
   1438     }
   1439 
   1440     /**
   1441      * Called when a device is removed or removal of device is detected.
   1442      *
   1443      * @param address a logical address of a device to be removed
   1444      */
   1445     @ServiceThreadOnly
   1446     final void removeCecDevice(int address) {
   1447         assertRunOnServiceThread();
   1448         HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
   1449 
   1450         mCecMessageCache.flushMessagesFrom(address);
   1451         invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
   1452     }
   1453 
   1454     @ServiceThreadOnly
   1455     void handleRemoveActiveRoutingPath(int path) {
   1456         assertRunOnServiceThread();
   1457         // Seq #23
   1458         if (isTailOfActivePath(path, getActivePath())) {
   1459             int newPath = mService.portIdToPath(getActivePortId());
   1460             startRoutingControl(getActivePath(), newPath, true, null);
   1461         }
   1462     }
   1463 
   1464     /**
   1465      * Launch routing control process.
   1466      *
   1467      * @param routingForBootup true if routing control is initiated due to One Touch Play
   1468      *        or TV power on
   1469      */
   1470     @ServiceThreadOnly
   1471     void launchRoutingControl(boolean routingForBootup) {
   1472         assertRunOnServiceThread();
   1473         // Seq #24
   1474         if (getActivePortId() != Constants.INVALID_PORT_ID) {
   1475             if (!routingForBootup && !isProhibitMode()) {
   1476                 int newPath = mService.portIdToPath(getActivePortId());
   1477                 setActivePath(newPath);
   1478                 startRoutingControl(getActivePath(), newPath, routingForBootup, null);
   1479             }
   1480         } else {
   1481             int activePath = mService.getPhysicalAddress();
   1482             setActivePath(activePath);
   1483             if (!routingForBootup
   1484                     && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
   1485                 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
   1486                         activePath));
   1487             }
   1488         }
   1489     }
   1490 
   1491     /**
   1492      * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
   1493      * the given routing path. CEC devices use routing path for its physical address to
   1494      * describe the hierarchy of the devices in the network.
   1495      *
   1496      * @param path routing path or physical address
   1497      * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
   1498      */
   1499     @ServiceThreadOnly
   1500     final HdmiDeviceInfo getDeviceInfoByPath(int path) {
   1501         assertRunOnServiceThread();
   1502         for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
   1503             if (info.getPhysicalAddress() == path) {
   1504                 return info;
   1505             }
   1506         }
   1507         return null;
   1508     }
   1509 
   1510     /**
   1511      * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
   1512      * the given routing path. This is the version accessible safely from threads
   1513      * other than service thread.
   1514      *
   1515      * @param path routing path or physical address
   1516      * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
   1517      */
   1518     HdmiDeviceInfo getSafeDeviceInfoByPath(int path) {
   1519         synchronized (mLock) {
   1520             for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
   1521                 if (info.getPhysicalAddress() == path) {
   1522                     return info;
   1523                 }
   1524             }
   1525             return null;
   1526         }
   1527     }
   1528 
   1529     /**
   1530      * Whether a device of the specified physical address and logical address exists
   1531      * in a device info list. However, both are minimal condition and it could
   1532      * be different device from the original one.
   1533      *
   1534      * @param logicalAddress logical address of a device to be searched
   1535      * @param physicalAddress physical address of a device to be searched
   1536      * @return true if exist; otherwise false
   1537      */
   1538     @ServiceThreadOnly
   1539     boolean isInDeviceList(int logicalAddress, int physicalAddress) {
   1540         assertRunOnServiceThread();
   1541         HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
   1542         if (device == null) {
   1543             return false;
   1544         }
   1545         return device.getPhysicalAddress() == physicalAddress;
   1546     }
   1547 
   1548     @Override
   1549     @ServiceThreadOnly
   1550     void onHotplug(int portId, boolean connected) {
   1551         assertRunOnServiceThread();
   1552 
   1553         if (!connected) {
   1554             removeCecSwitches(portId);
   1555         }
   1556         // Tv device will have permanent HotplugDetectionAction.
   1557         List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
   1558         if (!hotplugActions.isEmpty()) {
   1559             // Note that hotplug action is single action running on a machine.
   1560             // "pollAllDevicesNow" cleans up timer and start poll action immediately.
   1561             // It covers seq #40, #43.
   1562             hotplugActions.get(0).pollAllDevicesNow();
   1563         }
   1564     }
   1565 
   1566     private void removeCecSwitches(int portId) {
   1567         Iterator<Integer> it = mCecSwitches.iterator();
   1568         while (!it.hasNext()) {
   1569             int path = it.next();
   1570             if (pathToPortId(path) == portId) {
   1571                 it.remove();
   1572             }
   1573         }
   1574     }
   1575 
   1576     @Override
   1577     @ServiceThreadOnly
   1578     void setAutoDeviceOff(boolean enabled) {
   1579         assertRunOnServiceThread();
   1580         mAutoDeviceOff = enabled;
   1581     }
   1582 
   1583     @ServiceThreadOnly
   1584     void setAutoWakeup(boolean enabled) {
   1585         assertRunOnServiceThread();
   1586         mAutoWakeup = enabled;
   1587     }
   1588 
   1589     @ServiceThreadOnly
   1590     boolean getAutoWakeup() {
   1591         assertRunOnServiceThread();
   1592         return mAutoWakeup;
   1593     }
   1594 
   1595     @Override
   1596     @ServiceThreadOnly
   1597     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
   1598         assertRunOnServiceThread();
   1599         mService.unregisterTvInputCallback(mTvInputCallback);
   1600         // Remove any repeated working actions.
   1601         // HotplugDetectionAction will be reinstated during the wake up process.
   1602         // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
   1603         //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
   1604         removeAction(DeviceDiscoveryAction.class);
   1605         removeAction(HotplugDetectionAction.class);
   1606         removeAction(PowerStatusMonitorAction.class);
   1607         // Remove recording actions.
   1608         removeAction(OneTouchRecordAction.class);
   1609         removeAction(TimerRecordingAction.class);
   1610 
   1611         disableSystemAudioIfExist();
   1612         disableArcIfExist();
   1613 
   1614         super.disableDevice(initiatedByCec, callback);
   1615         clearDeviceInfoList();
   1616         getActiveSource().invalidate();
   1617         setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
   1618         checkIfPendingActionsCleared();
   1619     }
   1620 
   1621     @ServiceThreadOnly
   1622     private void disableSystemAudioIfExist() {
   1623         assertRunOnServiceThread();
   1624         if (getAvrDeviceInfo() == null) {
   1625             return;
   1626         }
   1627 
   1628         // Seq #31.
   1629         removeAction(SystemAudioActionFromAvr.class);
   1630         removeAction(SystemAudioActionFromTv.class);
   1631         removeAction(SystemAudioAutoInitiationAction.class);
   1632         removeAction(SystemAudioStatusAction.class);
   1633         removeAction(VolumeControlAction.class);
   1634     }
   1635 
   1636     @ServiceThreadOnly
   1637     private void disableArcIfExist() {
   1638         assertRunOnServiceThread();
   1639         HdmiDeviceInfo avr = getAvrDeviceInfo();
   1640         if (avr == null) {
   1641             return;
   1642         }
   1643 
   1644         // Seq #44.
   1645         removeAction(RequestArcInitiationAction.class);
   1646         if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
   1647             addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
   1648         }
   1649     }
   1650 
   1651     @Override
   1652     @ServiceThreadOnly
   1653     protected void onStandby(boolean initiatedByCec, int standbyAction) {
   1654         assertRunOnServiceThread();
   1655         // Seq #11
   1656         if (!mService.isControlEnabled()) {
   1657             return;
   1658         }
   1659         if (!initiatedByCec && mAutoDeviceOff) {
   1660             mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
   1661                     mAddress, Constants.ADDR_BROADCAST));
   1662         }
   1663     }
   1664 
   1665     boolean isProhibitMode() {
   1666         return mService.isProhibitMode();
   1667     }
   1668 
   1669     boolean isPowerStandbyOrTransient() {
   1670         return mService.isPowerStandbyOrTransient();
   1671     }
   1672 
   1673     @ServiceThreadOnly
   1674     void displayOsd(int messageId) {
   1675         assertRunOnServiceThread();
   1676         mService.displayOsd(messageId);
   1677     }
   1678 
   1679     @ServiceThreadOnly
   1680     void displayOsd(int messageId, int extra) {
   1681         assertRunOnServiceThread();
   1682         mService.displayOsd(messageId, extra);
   1683     }
   1684 
   1685     // Seq #54 and #55
   1686     @ServiceThreadOnly
   1687     int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
   1688         assertRunOnServiceThread();
   1689         if (!mService.isControlEnabled()) {
   1690             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
   1691             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
   1692             return Constants.ABORT_NOT_IN_CORRECT_MODE;
   1693         }
   1694 
   1695         if (!checkRecorder(recorderAddress)) {
   1696             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
   1697             announceOneTouchRecordResult(recorderAddress,
   1698                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
   1699             return Constants.ABORT_NOT_IN_CORRECT_MODE;
   1700         }
   1701 
   1702         if (!checkRecordSource(recordSource)) {
   1703             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
   1704             announceOneTouchRecordResult(recorderAddress,
   1705                     ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
   1706             return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
   1707         }
   1708 
   1709         addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
   1710         Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
   1711                 + Arrays.toString(recordSource));
   1712         return Constants.ABORT_NO_ERROR;
   1713     }
   1714 
   1715     @ServiceThreadOnly
   1716     void stopOneTouchRecord(int recorderAddress) {
   1717         assertRunOnServiceThread();
   1718         if (!mService.isControlEnabled()) {
   1719             Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
   1720             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
   1721             return;
   1722         }
   1723 
   1724         if (!checkRecorder(recorderAddress)) {
   1725             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
   1726             announceOneTouchRecordResult(recorderAddress,
   1727                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
   1728             return;
   1729         }
   1730 
   1731         // Remove one touch record action so that other one touch record can be started.
   1732         removeAction(OneTouchRecordAction.class);
   1733         mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress));
   1734         Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
   1735     }
   1736 
   1737     private boolean checkRecorder(int recorderAddress) {
   1738         HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
   1739         return (device != null)
   1740                 && (HdmiUtils.getTypeFromAddress(recorderAddress)
   1741                         == HdmiDeviceInfo.DEVICE_RECORDER);
   1742     }
   1743 
   1744     private boolean checkRecordSource(byte[] recordSource) {
   1745         return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
   1746     }
   1747 
   1748     @ServiceThreadOnly
   1749     void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
   1750         assertRunOnServiceThread();
   1751         if (!mService.isControlEnabled()) {
   1752             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
   1753             announceTimerRecordingResult(recorderAddress,
   1754                     TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
   1755             return;
   1756         }
   1757 
   1758         if (!checkRecorder(recorderAddress)) {
   1759             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
   1760             announceTimerRecordingResult(recorderAddress,
   1761                     TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
   1762             return;
   1763         }
   1764 
   1765         if (!checkTimerRecordingSource(sourceType, recordSource)) {
   1766             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
   1767             announceTimerRecordingResult(
   1768                     recorderAddress,
   1769                     TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
   1770             return;
   1771         }
   1772 
   1773         addAndStartAction(
   1774                 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
   1775         Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
   1776                 + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
   1777     }
   1778 
   1779     private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
   1780         return (recordSource != null)
   1781                 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
   1782     }
   1783 
   1784     @ServiceThreadOnly
   1785     void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
   1786         assertRunOnServiceThread();
   1787         if (!mService.isControlEnabled()) {
   1788             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
   1789             announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
   1790             return;
   1791         }
   1792 
   1793         if (!checkRecorder(recorderAddress)) {
   1794             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
   1795             announceClearTimerRecordingResult(recorderAddress,
   1796                     CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
   1797             return;
   1798         }
   1799 
   1800         if (!checkTimerRecordingSource(sourceType, recordSource)) {
   1801             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
   1802             announceClearTimerRecordingResult(recorderAddress,
   1803                     CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
   1804             return;
   1805         }
   1806 
   1807         sendClearTimerMessage(recorderAddress, sourceType, recordSource);
   1808     }
   1809 
   1810     private void sendClearTimerMessage(final int recorderAddress, int sourceType,
   1811             byte[] recordSource) {
   1812         HdmiCecMessage message = null;
   1813         switch (sourceType) {
   1814             case TIMER_RECORDING_TYPE_DIGITAL:
   1815                 message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress,
   1816                         recordSource);
   1817                 break;
   1818             case TIMER_RECORDING_TYPE_ANALOGUE:
   1819                 message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress,
   1820                         recordSource);
   1821                 break;
   1822             case TIMER_RECORDING_TYPE_EXTERNAL:
   1823                 message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress,
   1824                         recordSource);
   1825                 break;
   1826             default:
   1827                 Slog.w(TAG, "Invalid source type:" + recorderAddress);
   1828                 announceClearTimerRecordingResult(recorderAddress,
   1829                         CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
   1830                 return;
   1831 
   1832         }
   1833         mService.sendCecCommand(message, new SendMessageCallback() {
   1834             @Override
   1835             public void onSendCompleted(int error) {
   1836                 if (error != SendMessageResult.SUCCESS) {
   1837                     announceClearTimerRecordingResult(recorderAddress,
   1838                             CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
   1839                 }
   1840             }
   1841         });
   1842     }
   1843 
   1844     void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
   1845         HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
   1846         if (info == null) {
   1847             Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
   1848             return;
   1849         }
   1850 
   1851         if (info.getDevicePowerStatus() == newPowerStatus) {
   1852             return;
   1853         }
   1854 
   1855         HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
   1856         // addDeviceInfo replaces old device info with new one if exists.
   1857         addDeviceInfo(newInfo);
   1858 
   1859         invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
   1860     }
   1861 
   1862     @Override
   1863     protected boolean handleMenuStatus(HdmiCecMessage message) {
   1864         // Do nothing and just return true not to prevent from responding <Feature Abort>.
   1865         return true;
   1866     }
   1867 
   1868     @Override
   1869     protected void sendStandby(int deviceId) {
   1870         HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
   1871         if (targetDevice == null) {
   1872             return;
   1873         }
   1874         int targetAddress = targetDevice.getLogicalAddress();
   1875         mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
   1876     }
   1877 
   1878     @ServiceThreadOnly
   1879     void processAllDelayedMessages() {
   1880         assertRunOnServiceThread();
   1881         mDelayedMessageBuffer.processAllMessages();
   1882     }
   1883 
   1884     @ServiceThreadOnly
   1885     void processDelayedMessages(int address) {
   1886         assertRunOnServiceThread();
   1887         mDelayedMessageBuffer.processMessagesForDevice(address);
   1888     }
   1889 
   1890     @ServiceThreadOnly
   1891     void processDelayedActiveSource(int address) {
   1892         assertRunOnServiceThread();
   1893         mDelayedMessageBuffer.processActiveSource(address);
   1894     }
   1895 
   1896     @Override
   1897     protected void dump(final IndentingPrintWriter pw) {
   1898         super.dump(pw);
   1899         pw.println("mArcEstablished: " + mArcEstablished);
   1900         pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
   1901         pw.println("mSystemAudioMute: " + mSystemAudioMute);
   1902         pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
   1903         pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
   1904         pw.println("mAutoWakeup: " + mAutoWakeup);
   1905         pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
   1906         pw.println("mPrevPortId: " + mPrevPortId);
   1907         pw.println("CEC devices:");
   1908         pw.increaseIndent();
   1909         for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
   1910             pw.println(info);
   1911         }
   1912         pw.decreaseIndent();
   1913     }
   1914 }
   1915