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