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         boolean mute = HdmiUtils.isAudioStatusMute(message);
    702         int volume = HdmiUtils.getAudioStatusVolume(message);
    703         setAudioStatus(mute, volume);
    704         return true;
    705     }
    706 
    707     @Override
    708     @ServiceThreadOnly
    709     protected boolean handleTextViewOn(HdmiCecMessage message) {
    710         assertRunOnServiceThread();
    711 
    712         // Note that <Text View On> (and <Image View On>) command won't be handled here in
    713         // most cases. A dedicated microcontroller should be in charge while Android system
    714         // is in sleep mode, and the command need not be passed up to this service.
    715         // The only situation where the command reaches this handler is that sleep mode is
    716         // implemented in such a way that Android system is not really put to standby mode
    717         // but only the display is set to blank. Then the command leads to the effect of
    718         // turning on the display by the invocation of PowerManager.wakeUp().
    719         if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
    720             mService.wakeUp();
    721         }
    722         return true;
    723     }
    724 
    725     @Override
    726     @ServiceThreadOnly
    727     protected boolean handleImageViewOn(HdmiCecMessage message) {
    728         assertRunOnServiceThread();
    729         // Currently, it's the same as <Text View On>.
    730         return handleTextViewOn(message);
    731     }
    732 
    733     @Override
    734     @ServiceThreadOnly
    735     protected boolean handleSetOsdName(HdmiCecMessage message) {
    736         int source = message.getSource();
    737         HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
    738         // If the device is not in device list, ignore it.
    739         if (deviceInfo == null) {
    740             Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
    741             return true;
    742         }
    743         String osdName = null;
    744         try {
    745             osdName = new String(message.getParams(), "US-ASCII");
    746         } catch (UnsupportedEncodingException e) {
    747             Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
    748             return true;
    749         }
    750 
    751         if (deviceInfo.getDisplayName().equals(osdName)) {
    752             Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
    753             return true;
    754         }
    755 
    756         addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
    757                 deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
    758                 deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
    759         return true;
    760     }
    761 
    762     @ServiceThreadOnly
    763     private void launchDeviceDiscovery() {
    764         assertRunOnServiceThread();
    765         clearDeviceInfoList();
    766         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
    767                 new DeviceDiscoveryCallback() {
    768                     @Override
    769                     public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
    770                         for (HdmiDeviceInfo info : deviceInfos) {
    771                             addCecDevice(info);
    772                         }
    773 
    774                         // Since we removed all devices when it's start and
    775                         // device discovery action does not poll local devices,
    776                         // we should put device info of local device manually here
    777                         for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
    778                             addCecDevice(device.getDeviceInfo());
    779                         }
    780 
    781                         mSelectRequestBuffer.process();
    782                         resetSelectRequestBuffer();
    783 
    784                         addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
    785                         addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
    786 
    787                         HdmiDeviceInfo avr = getAvrDeviceInfo();
    788                         if (avr != null) {
    789                             onNewAvrAdded(avr);
    790                         } else {
    791                             setSystemAudioMode(false);
    792                         }
    793                     }
    794                 });
    795         addAndStartAction(action);
    796     }
    797 
    798     @ServiceThreadOnly
    799     void onNewAvrAdded(HdmiDeviceInfo avr) {
    800         assertRunOnServiceThread();
    801         addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
    802         if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId())
    803                 && !hasAction(SetArcTransmissionStateAction.class)) {
    804             startArcAction(true);
    805         }
    806     }
    807 
    808     // Clear all device info.
    809     @ServiceThreadOnly
    810     private void clearDeviceInfoList() {
    811         assertRunOnServiceThread();
    812         for (HdmiDeviceInfo info : mSafeExternalInputs) {
    813             invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
    814         }
    815         mDeviceInfos.clear();
    816         updateSafeDeviceInfoList();
    817     }
    818 
    819     @ServiceThreadOnly
    820     // Seq #32
    821     void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
    822         assertRunOnServiceThread();
    823         if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
    824             setSystemAudioMode(false);
    825             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
    826             return;
    827         }
    828         HdmiDeviceInfo avr = getAvrDeviceInfo();
    829         if (avr == null) {
    830             setSystemAudioMode(false);
    831             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
    832             return;
    833         }
    834 
    835         addAndStartAction(
    836                 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
    837     }
    838 
    839     // # Seq 25
    840     void setSystemAudioMode(boolean on) {
    841         if (!isSystemAudioControlFeatureEnabled() && on) {
    842             HdmiLogger.debug("Cannot turn on system audio mode "
    843                     + "because the System Audio Control feature is disabled.");
    844             return;
    845         }
    846         HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on);
    847         updateAudioManagerForSystemAudio(on);
    848         synchronized (mLock) {
    849             if (mSystemAudioActivated != on) {
    850                 mSystemAudioActivated = on;
    851                 mService.announceSystemAudioModeChange(on);
    852             }
    853             startArcAction(on);
    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         if (!isSystemAudioActivated()) {
   1007             return;
   1008         }
   1009         synchronized (mLock) {
   1010             mSystemAudioMute = mute;
   1011             mSystemAudioVolume = volume;
   1012             int maxVolume = mService.getAudioManager().getStreamMaxVolume(
   1013                     AudioManager.STREAM_MUSIC);
   1014             mService.setAudioStatus(mute,
   1015                     VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
   1016             displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
   1017                     mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
   1018         }
   1019     }
   1020 
   1021     @ServiceThreadOnly
   1022     void changeVolume(int curVolume, int delta, int maxVolume) {
   1023         assertRunOnServiceThread();
   1024         if (getAvrDeviceInfo() == null) {
   1025             // On initialization process, getAvrDeviceInfo() may return null and cause exception
   1026             return;
   1027         }
   1028         if (delta == 0 || !isSystemAudioActivated()) {
   1029             return;
   1030         }
   1031 
   1032         int targetVolume = curVolume + delta;
   1033         int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
   1034         synchronized (mLock) {
   1035             // If new volume is the same as current system audio volume, just ignore it.
   1036             // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
   1037             if (cecVolume == mSystemAudioVolume) {
   1038                 // Update tv volume with system volume value.
   1039                 mService.setAudioStatus(false,
   1040                         VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
   1041                 return;
   1042             }
   1043         }
   1044 
   1045         List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
   1046         if (actions.isEmpty()) {
   1047             addAndStartAction(new VolumeControlAction(this,
   1048                     getAvrDeviceInfo().getLogicalAddress(), delta > 0));
   1049         } else {
   1050             actions.get(0).handleVolumeChange(delta > 0);
   1051         }
   1052     }
   1053 
   1054     @ServiceThreadOnly
   1055     void changeMute(boolean mute) {
   1056         assertRunOnServiceThread();
   1057         if (getAvrDeviceInfo() == null) {
   1058             // On initialization process, getAvrDeviceInfo() may return null and cause exception
   1059             return;
   1060         }
   1061         HdmiLogger.debug("[A]:Change mute:%b", mute);
   1062         synchronized (mLock) {
   1063             if (mSystemAudioMute == mute) {
   1064                 HdmiLogger.debug("No need to change mute.");
   1065                 return;
   1066             }
   1067         }
   1068         if (!isSystemAudioActivated()) {
   1069             HdmiLogger.debug("[A]:System audio is not activated.");
   1070             return;
   1071         }
   1072 
   1073         // Remove existing volume action.
   1074         removeAction(VolumeControlAction.class);
   1075         sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
   1076                 HdmiCecKeycode.getMuteKey(mute));
   1077     }
   1078 
   1079     @Override
   1080     @ServiceThreadOnly
   1081     protected boolean handleInitiateArc(HdmiCecMessage message) {
   1082         assertRunOnServiceThread();
   1083 
   1084         if (!canStartArcUpdateAction(message.getSource(), true)) {
   1085             if (getAvrDeviceInfo() == null) {
   1086                 // AVR may not have been discovered yet. Delay the message processing.
   1087                 mDelayedMessageBuffer.add(message);
   1088                 return true;
   1089             }
   1090             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
   1091             if (!isConnectedToArcPort(message.getSource())) {
   1092                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
   1093             }
   1094             return true;
   1095         }
   1096 
   1097         // In case where <Initiate Arc> is started by <Request ARC Initiation>
   1098         // need to clean up RequestArcInitiationAction.
   1099         removeAction(RequestArcInitiationAction.class);
   1100         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
   1101                 message.getSource(), true);
   1102         addAndStartAction(action);
   1103         return true;
   1104     }
   1105 
   1106     private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) {
   1107         HdmiDeviceInfo avr = getAvrDeviceInfo();
   1108         if (avr != null
   1109                 && (avrAddress == avr.getLogicalAddress())
   1110                 && isConnectedToArcPort(avr.getPhysicalAddress())
   1111                 && isDirectConnectAddress(avr.getPhysicalAddress())) {
   1112             if (enabled) {
   1113                 return isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId());
   1114             } else {
   1115                 return true;
   1116             }
   1117         } else {
   1118             return false;
   1119         }
   1120     }
   1121 
   1122     @Override
   1123     @ServiceThreadOnly
   1124     protected boolean handleTerminateArc(HdmiCecMessage message) {
   1125         assertRunOnServiceThread();
   1126         if (mService .isPowerStandbyOrTransient()) {
   1127             setArcStatus(false);
   1128             return true;
   1129         }
   1130         // Do not check ARC configuration since the AVR might have been already removed.
   1131         // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by
   1132         // <Request ARC Termination>.
   1133         removeAction(RequestArcTerminationAction.class);
   1134         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
   1135                 message.getSource(), false);
   1136         addAndStartAction(action);
   1137         return true;
   1138     }
   1139 
   1140     @Override
   1141     @ServiceThreadOnly
   1142     protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
   1143         assertRunOnServiceThread();
   1144         boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message);
   1145         if (!isMessageForSystemAudio(message)) {
   1146             if (getAvrDeviceInfo() == null) {
   1147                 // AVR may not have been discovered yet. Delay the message processing.
   1148                 mDelayedMessageBuffer.add(message);
   1149             } else {
   1150                 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
   1151                 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
   1152             }
   1153             return true;
   1154         } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) {
   1155             HdmiLogger.debug("Ignoring <Set System Audio Mode> message "
   1156                     + "because the System Audio Control feature is disabled: %s", message);
   1157             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
   1158             return true;
   1159         }
   1160         removeAction(SystemAudioAutoInitiationAction.class);
   1161         SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
   1162                 message.getSource(), systemAudioStatus, null);
   1163         addAndStartAction(action);
   1164         return true;
   1165     }
   1166 
   1167     @Override
   1168     @ServiceThreadOnly
   1169     protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
   1170         assertRunOnServiceThread();
   1171         if (!isMessageForSystemAudio(message)) {
   1172             HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
   1173             // Ignore this message.
   1174             return true;
   1175         }
   1176         setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message));
   1177         return true;
   1178     }
   1179 
   1180     // Seq #53
   1181     @Override
   1182     @ServiceThreadOnly
   1183     protected boolean handleRecordTvScreen(HdmiCecMessage message) {
   1184         List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
   1185         if (!actions.isEmpty()) {
   1186             // Assumes only one OneTouchRecordAction.
   1187             OneTouchRecordAction action = actions.get(0);
   1188             if (action.getRecorderAddress() != message.getSource()) {
   1189                 announceOneTouchRecordResult(
   1190                         message.getSource(),
   1191                         HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
   1192             }
   1193             return super.handleRecordTvScreen(message);
   1194         }
   1195 
   1196         int recorderAddress = message.getSource();
   1197         byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
   1198         int reason = startOneTouchRecord(recorderAddress, recordSource);
   1199         if (reason != Constants.ABORT_NO_ERROR) {
   1200             mService.maySendFeatureAbortCommand(message, reason);
   1201         }
   1202         return true;
   1203     }
   1204 
   1205     @Override
   1206     protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
   1207         byte[] params = message.getParams();
   1208         int timerClearedStatusData = params[0] & 0xFF;
   1209         announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
   1210         return true;
   1211     }
   1212 
   1213     void announceOneTouchRecordResult(int recorderAddress, int result) {
   1214         mService.invokeOneTouchRecordResult(recorderAddress, result);
   1215     }
   1216 
   1217     void announceTimerRecordingResult(int recorderAddress, int result) {
   1218         mService.invokeTimerRecordingResult(recorderAddress, result);
   1219     }
   1220 
   1221     void announceClearTimerRecordingResult(int recorderAddress, int result) {
   1222         mService.invokeClearTimerRecordingResult(recorderAddress, result);
   1223     }
   1224 
   1225     private boolean isMessageForSystemAudio(HdmiCecMessage message) {
   1226         return mService.isControlEnabled()
   1227                 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
   1228                 && (message.getDestination() == Constants.ADDR_TV
   1229                         || message.getDestination() == Constants.ADDR_BROADCAST)
   1230                 && getAvrDeviceInfo() != null;
   1231     }
   1232 
   1233     /**
   1234      * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
   1235      * logical address as new device info's.
   1236      *
   1237      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
   1238      *
   1239      * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
   1240      * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
   1241      *         that has the same logical address as new one has.
   1242      */
   1243     @ServiceThreadOnly
   1244     private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
   1245         assertRunOnServiceThread();
   1246         HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
   1247         if (oldDeviceInfo != null) {
   1248             removeDeviceInfo(deviceInfo.getId());
   1249         }
   1250         mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
   1251         updateSafeDeviceInfoList();
   1252         return oldDeviceInfo;
   1253     }
   1254 
   1255     /**
   1256      * Remove a device info corresponding to the given {@code logicalAddress}.
   1257      * It returns removed {@link HdmiDeviceInfo} if exists.
   1258      *
   1259      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
   1260      *
   1261      * @param id id of device to be removed
   1262      * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
   1263      */
   1264     @ServiceThreadOnly
   1265     private HdmiDeviceInfo removeDeviceInfo(int id) {
   1266         assertRunOnServiceThread();
   1267         HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
   1268         if (deviceInfo != null) {
   1269             mDeviceInfos.remove(id);
   1270         }
   1271         updateSafeDeviceInfoList();
   1272         return deviceInfo;
   1273     }
   1274 
   1275     /**
   1276      * Return a list of all {@link HdmiDeviceInfo}.
   1277      *
   1278      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
   1279      * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
   1280      * does not include local device.
   1281      */
   1282     @ServiceThreadOnly
   1283     List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
   1284         assertRunOnServiceThread();
   1285         if (includeLocalDevice) {
   1286             return HdmiUtils.sparseArrayToList(mDeviceInfos);
   1287         } else {
   1288             ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
   1289             for (int i = 0; i < mDeviceInfos.size(); ++i) {
   1290                 HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
   1291                 if (!isLocalDeviceAddress(info.getLogicalAddress())) {
   1292                     infoList.add(info);
   1293                 }
   1294             }
   1295             return infoList;
   1296         }
   1297     }
   1298 
   1299     /**
   1300      * Return external input devices.
   1301      */
   1302     @GuardedBy("mLock")
   1303     List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
   1304         return mSafeExternalInputs;
   1305     }
   1306 
   1307     @ServiceThreadOnly
   1308     private void updateSafeDeviceInfoList() {
   1309         assertRunOnServiceThread();
   1310         List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
   1311         List<HdmiDeviceInfo> externalInputs = getInputDevices();
   1312         synchronized (mLock) {
   1313             mSafeAllDeviceInfos = copiedDevices;
   1314             mSafeExternalInputs = externalInputs;
   1315         }
   1316     }
   1317 
   1318     /**
   1319      * Return a list of external cec input (source) devices.
   1320      *
   1321      * <p>Note that this effectively excludes non-source devices like system audio,
   1322      * secondary TV.
   1323      */
   1324     private List<HdmiDeviceInfo> getInputDevices() {
   1325         ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
   1326         for (int i = 0; i < mDeviceInfos.size(); ++i) {
   1327             HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
   1328             if (isLocalDeviceAddress(info.getLogicalAddress())) {
   1329                 continue;
   1330             }
   1331             if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
   1332                 infoList.add(info);
   1333             }
   1334         }
   1335         return infoList;
   1336     }
   1337 
   1338     // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
   1339     // Returns true if the policy is set to true, and the device to check does not have
   1340     // a parent CEC device (which should be the CEC-enabled switch) in the list.
   1341     private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
   1342         return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
   1343                 && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
   1344     }
   1345 
   1346     private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
   1347         for (int switchPath : switches) {
   1348             if (isParentPath(switchPath, path)) {
   1349                 return true;
   1350             }
   1351         }
   1352         return false;
   1353     }
   1354 
   1355     private static boolean isParentPath(int parentPath, int childPath) {
   1356         // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
   1357         // If child's last non-zero nibble is removed, the result equals to the parent.
   1358         for (int i = 0; i <= 12; i += 4) {
   1359             int nibble = (childPath >> i) & 0xF;
   1360             if (nibble != 0) {
   1361                 int parentNibble = (parentPath >> i) & 0xF;
   1362                 return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
   1363             }
   1364         }
   1365         return false;
   1366     }
   1367 
   1368     private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
   1369         if (!hideDevicesBehindLegacySwitch(info)) {
   1370             mService.invokeDeviceEventListeners(info, status);
   1371         }
   1372     }
   1373 
   1374     private boolean isLocalDeviceAddress(int address) {
   1375         return mLocalDeviceAddresses.contains(address);
   1376     }
   1377 
   1378     @ServiceThreadOnly
   1379     HdmiDeviceInfo getAvrDeviceInfo() {
   1380         assertRunOnServiceThread();
   1381         return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
   1382     }
   1383 
   1384     /**
   1385      * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
   1386      *
   1387      * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
   1388      *
   1389      * @param logicalAddress logical address of the device to be retrieved
   1390      * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
   1391      *         Returns null if no logical address matched
   1392      */
   1393     @ServiceThreadOnly
   1394     HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
   1395         assertRunOnServiceThread();
   1396         return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
   1397     }
   1398 
   1399     boolean hasSystemAudioDevice() {
   1400         return getSafeAvrDeviceInfo() != null;
   1401     }
   1402 
   1403     HdmiDeviceInfo getSafeAvrDeviceInfo() {
   1404         return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
   1405     }
   1406 
   1407     /**
   1408      * Thread safe version of {@link #getCecDeviceInfo(int)}.
   1409      *
   1410      * @param logicalAddress logical address to be retrieved
   1411      * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
   1412      *         Returns null if no logical address matched
   1413      */
   1414     HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
   1415         synchronized (mLock) {
   1416             for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
   1417                 if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
   1418                     return info;
   1419                 }
   1420             }
   1421             return null;
   1422         }
   1423     }
   1424 
   1425     @GuardedBy("mLock")
   1426     List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
   1427         ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
   1428         for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
   1429             if (isLocalDeviceAddress(info.getLogicalAddress())) {
   1430                 continue;
   1431             }
   1432             infoList.add(info);
   1433         }
   1434         return infoList;
   1435     }
   1436 
   1437     /**
   1438      * Called when a device is newly added or a new device is detected or
   1439      * existing device is updated.
   1440      *
   1441      * @param info device info of a new device.
   1442      */
   1443     @ServiceThreadOnly
   1444     final void addCecDevice(HdmiDeviceInfo info) {
   1445         assertRunOnServiceThread();
   1446         HdmiDeviceInfo old = addDeviceInfo(info);
   1447         if (info.getLogicalAddress() == mAddress) {
   1448             // The addition of TV device itself should not be notified.
   1449             return;
   1450         }
   1451         if (old == null) {
   1452             invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
   1453         } else if (!old.equals(info)) {
   1454             invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
   1455             invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
   1456         }
   1457     }
   1458 
   1459     /**
   1460      * Called when a device is removed or removal of device is detected.
   1461      *
   1462      * @param address a logical address of a device to be removed
   1463      */
   1464     @ServiceThreadOnly
   1465     final void removeCecDevice(int address) {
   1466         assertRunOnServiceThread();
   1467         HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
   1468 
   1469         mCecMessageCache.flushMessagesFrom(address);
   1470         invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
   1471     }
   1472 
   1473     @ServiceThreadOnly
   1474     void handleRemoveActiveRoutingPath(int path) {
   1475         assertRunOnServiceThread();
   1476         // Seq #23
   1477         if (isTailOfActivePath(path, getActivePath())) {
   1478             int newPath = mService.portIdToPath(getActivePortId());
   1479             startRoutingControl(getActivePath(), newPath, true, null);
   1480         }
   1481     }
   1482 
   1483     /**
   1484      * Launch routing control process.
   1485      *
   1486      * @param routingForBootup true if routing control is initiated due to One Touch Play
   1487      *        or TV power on
   1488      */
   1489     @ServiceThreadOnly
   1490     void launchRoutingControl(boolean routingForBootup) {
   1491         assertRunOnServiceThread();
   1492         // Seq #24
   1493         if (getActivePortId() != Constants.INVALID_PORT_ID) {
   1494             if (!routingForBootup && !isProhibitMode()) {
   1495                 int newPath = mService.portIdToPath(getActivePortId());
   1496                 setActivePath(newPath);
   1497                 startRoutingControl(getActivePath(), newPath, routingForBootup, null);
   1498             }
   1499         } else {
   1500             int activePath = mService.getPhysicalAddress();
   1501             setActivePath(activePath);
   1502             if (!routingForBootup
   1503                     && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
   1504                 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
   1505                         activePath));
   1506             }
   1507         }
   1508     }
   1509 
   1510     /**
   1511      * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
   1512      * the given routing path. CEC devices use routing path for its physical address to
   1513      * describe the hierarchy of the devices in the network.
   1514      *
   1515      * @param path routing path or physical address
   1516      * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
   1517      */
   1518     @ServiceThreadOnly
   1519     final HdmiDeviceInfo getDeviceInfoByPath(int path) {
   1520         assertRunOnServiceThread();
   1521         for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
   1522             if (info.getPhysicalAddress() == path) {
   1523                 return info;
   1524             }
   1525         }
   1526         return null;
   1527     }
   1528 
   1529     /**
   1530      * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
   1531      * the given routing path. This is the version accessible safely from threads
   1532      * other than service thread.
   1533      *
   1534      * @param path routing path or physical address
   1535      * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
   1536      */
   1537     HdmiDeviceInfo getSafeDeviceInfoByPath(int path) {
   1538         synchronized (mLock) {
   1539             for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
   1540                 if (info.getPhysicalAddress() == path) {
   1541                     return info;
   1542                 }
   1543             }
   1544             return null;
   1545         }
   1546     }
   1547 
   1548     /**
   1549      * Whether a device of the specified physical address and logical address exists
   1550      * in a device info list. However, both are minimal condition and it could
   1551      * be different device from the original one.
   1552      *
   1553      * @param logicalAddress logical address of a device to be searched
   1554      * @param physicalAddress physical address of a device to be searched
   1555      * @return true if exist; otherwise false
   1556      */
   1557     @ServiceThreadOnly
   1558     boolean isInDeviceList(int logicalAddress, int physicalAddress) {
   1559         assertRunOnServiceThread();
   1560         HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
   1561         if (device == null) {
   1562             return false;
   1563         }
   1564         return device.getPhysicalAddress() == physicalAddress;
   1565     }
   1566 
   1567     @Override
   1568     @ServiceThreadOnly
   1569     void onHotplug(int portId, boolean connected) {
   1570         assertRunOnServiceThread();
   1571 
   1572         if (!connected) {
   1573             removeCecSwitches(portId);
   1574         }
   1575         // Tv device will have permanent HotplugDetectionAction.
   1576         List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
   1577         if (!hotplugActions.isEmpty()) {
   1578             // Note that hotplug action is single action running on a machine.
   1579             // "pollAllDevicesNow" cleans up timer and start poll action immediately.
   1580             // It covers seq #40, #43.
   1581             hotplugActions.get(0).pollAllDevicesNow();
   1582         }
   1583     }
   1584 
   1585     private void removeCecSwitches(int portId) {
   1586         Iterator<Integer> it = mCecSwitches.iterator();
   1587         while (!it.hasNext()) {
   1588             int path = it.next();
   1589             if (pathToPortId(path) == portId) {
   1590                 it.remove();
   1591             }
   1592         }
   1593     }
   1594 
   1595     @Override
   1596     @ServiceThreadOnly
   1597     void setAutoDeviceOff(boolean enabled) {
   1598         assertRunOnServiceThread();
   1599         mAutoDeviceOff = enabled;
   1600     }
   1601 
   1602     @ServiceThreadOnly
   1603     void setAutoWakeup(boolean enabled) {
   1604         assertRunOnServiceThread();
   1605         mAutoWakeup = enabled;
   1606     }
   1607 
   1608     @ServiceThreadOnly
   1609     boolean getAutoWakeup() {
   1610         assertRunOnServiceThread();
   1611         return mAutoWakeup;
   1612     }
   1613 
   1614     @Override
   1615     @ServiceThreadOnly
   1616     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
   1617         assertRunOnServiceThread();
   1618         mService.unregisterTvInputCallback(mTvInputCallback);
   1619         // Remove any repeated working actions.
   1620         // HotplugDetectionAction will be reinstated during the wake up process.
   1621         // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
   1622         //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
   1623         removeAction(DeviceDiscoveryAction.class);
   1624         removeAction(HotplugDetectionAction.class);
   1625         removeAction(PowerStatusMonitorAction.class);
   1626         // Remove recording actions.
   1627         removeAction(OneTouchRecordAction.class);
   1628         removeAction(TimerRecordingAction.class);
   1629 
   1630         disableSystemAudioIfExist();
   1631         disableArcIfExist();
   1632 
   1633         super.disableDevice(initiatedByCec, callback);
   1634         clearDeviceInfoList();
   1635         getActiveSource().invalidate();
   1636         setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
   1637         checkIfPendingActionsCleared();
   1638     }
   1639 
   1640     @ServiceThreadOnly
   1641     private void disableSystemAudioIfExist() {
   1642         assertRunOnServiceThread();
   1643         if (getAvrDeviceInfo() == null) {
   1644             return;
   1645         }
   1646 
   1647         // Seq #31.
   1648         removeAction(SystemAudioActionFromAvr.class);
   1649         removeAction(SystemAudioActionFromTv.class);
   1650         removeAction(SystemAudioAutoInitiationAction.class);
   1651         removeAction(SystemAudioStatusAction.class);
   1652         removeAction(VolumeControlAction.class);
   1653     }
   1654 
   1655     @ServiceThreadOnly
   1656     private void disableArcIfExist() {
   1657         assertRunOnServiceThread();
   1658         HdmiDeviceInfo avr = getAvrDeviceInfo();
   1659         if (avr == null) {
   1660             return;
   1661         }
   1662 
   1663         // Seq #44.
   1664         removeAction(RequestArcInitiationAction.class);
   1665         if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
   1666             addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
   1667         }
   1668     }
   1669 
   1670     @Override
   1671     @ServiceThreadOnly
   1672     protected void onStandby(boolean initiatedByCec, int standbyAction) {
   1673         assertRunOnServiceThread();
   1674         // Seq #11
   1675         if (!mService.isControlEnabled()) {
   1676             return;
   1677         }
   1678         if (!initiatedByCec && mAutoDeviceOff) {
   1679             mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
   1680                     mAddress, Constants.ADDR_BROADCAST));
   1681         }
   1682     }
   1683 
   1684     boolean isProhibitMode() {
   1685         return mService.isProhibitMode();
   1686     }
   1687 
   1688     boolean isPowerStandbyOrTransient() {
   1689         return mService.isPowerStandbyOrTransient();
   1690     }
   1691 
   1692     @ServiceThreadOnly
   1693     void displayOsd(int messageId) {
   1694         assertRunOnServiceThread();
   1695         mService.displayOsd(messageId);
   1696     }
   1697 
   1698     @ServiceThreadOnly
   1699     void displayOsd(int messageId, int extra) {
   1700         assertRunOnServiceThread();
   1701         mService.displayOsd(messageId, extra);
   1702     }
   1703 
   1704     // Seq #54 and #55
   1705     @ServiceThreadOnly
   1706     int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
   1707         assertRunOnServiceThread();
   1708         if (!mService.isControlEnabled()) {
   1709             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
   1710             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
   1711             return Constants.ABORT_NOT_IN_CORRECT_MODE;
   1712         }
   1713 
   1714         if (!checkRecorder(recorderAddress)) {
   1715             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
   1716             announceOneTouchRecordResult(recorderAddress,
   1717                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
   1718             return Constants.ABORT_NOT_IN_CORRECT_MODE;
   1719         }
   1720 
   1721         if (!checkRecordSource(recordSource)) {
   1722             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
   1723             announceOneTouchRecordResult(recorderAddress,
   1724                     ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
   1725             return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
   1726         }
   1727 
   1728         addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
   1729         Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
   1730                 + Arrays.toString(recordSource));
   1731         return Constants.ABORT_NO_ERROR;
   1732     }
   1733 
   1734     @ServiceThreadOnly
   1735     void stopOneTouchRecord(int recorderAddress) {
   1736         assertRunOnServiceThread();
   1737         if (!mService.isControlEnabled()) {
   1738             Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
   1739             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
   1740             return;
   1741         }
   1742 
   1743         if (!checkRecorder(recorderAddress)) {
   1744             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
   1745             announceOneTouchRecordResult(recorderAddress,
   1746                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
   1747             return;
   1748         }
   1749 
   1750         // Remove one touch record action so that other one touch record can be started.
   1751         removeAction(OneTouchRecordAction.class);
   1752         mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress));
   1753         Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
   1754     }
   1755 
   1756     private boolean checkRecorder(int recorderAddress) {
   1757         HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
   1758         return (device != null)
   1759                 && (HdmiUtils.getTypeFromAddress(recorderAddress)
   1760                         == HdmiDeviceInfo.DEVICE_RECORDER);
   1761     }
   1762 
   1763     private boolean checkRecordSource(byte[] recordSource) {
   1764         return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
   1765     }
   1766 
   1767     @ServiceThreadOnly
   1768     void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
   1769         assertRunOnServiceThread();
   1770         if (!mService.isControlEnabled()) {
   1771             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
   1772             announceTimerRecordingResult(recorderAddress,
   1773                     TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
   1774             return;
   1775         }
   1776 
   1777         if (!checkRecorder(recorderAddress)) {
   1778             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
   1779             announceTimerRecordingResult(recorderAddress,
   1780                     TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
   1781             return;
   1782         }
   1783 
   1784         if (!checkTimerRecordingSource(sourceType, recordSource)) {
   1785             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
   1786             announceTimerRecordingResult(
   1787                     recorderAddress,
   1788                     TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
   1789             return;
   1790         }
   1791 
   1792         addAndStartAction(
   1793                 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
   1794         Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
   1795                 + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
   1796     }
   1797 
   1798     private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
   1799         return (recordSource != null)
   1800                 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
   1801     }
   1802 
   1803     @ServiceThreadOnly
   1804     void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
   1805         assertRunOnServiceThread();
   1806         if (!mService.isControlEnabled()) {
   1807             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
   1808             announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
   1809             return;
   1810         }
   1811 
   1812         if (!checkRecorder(recorderAddress)) {
   1813             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
   1814             announceClearTimerRecordingResult(recorderAddress,
   1815                     CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
   1816             return;
   1817         }
   1818 
   1819         if (!checkTimerRecordingSource(sourceType, recordSource)) {
   1820             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
   1821             announceClearTimerRecordingResult(recorderAddress,
   1822                     CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
   1823             return;
   1824         }
   1825 
   1826         sendClearTimerMessage(recorderAddress, sourceType, recordSource);
   1827     }
   1828 
   1829     private void sendClearTimerMessage(final int recorderAddress, int sourceType,
   1830             byte[] recordSource) {
   1831         HdmiCecMessage message = null;
   1832         switch (sourceType) {
   1833             case TIMER_RECORDING_TYPE_DIGITAL:
   1834                 message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress,
   1835                         recordSource);
   1836                 break;
   1837             case TIMER_RECORDING_TYPE_ANALOGUE:
   1838                 message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress,
   1839                         recordSource);
   1840                 break;
   1841             case TIMER_RECORDING_TYPE_EXTERNAL:
   1842                 message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress,
   1843                         recordSource);
   1844                 break;
   1845             default:
   1846                 Slog.w(TAG, "Invalid source type:" + recorderAddress);
   1847                 announceClearTimerRecordingResult(recorderAddress,
   1848                         CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
   1849                 return;
   1850 
   1851         }
   1852         mService.sendCecCommand(message, new SendMessageCallback() {
   1853             @Override
   1854             public void onSendCompleted(int error) {
   1855                 if (error != SendMessageResult.SUCCESS) {
   1856                     announceClearTimerRecordingResult(recorderAddress,
   1857                             CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
   1858                 }
   1859             }
   1860         });
   1861     }
   1862 
   1863     void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
   1864         HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
   1865         if (info == null) {
   1866             Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
   1867             return;
   1868         }
   1869 
   1870         if (info.getDevicePowerStatus() == newPowerStatus) {
   1871             return;
   1872         }
   1873 
   1874         HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
   1875         // addDeviceInfo replaces old device info with new one if exists.
   1876         addDeviceInfo(newInfo);
   1877 
   1878         invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
   1879     }
   1880 
   1881     @Override
   1882     protected boolean handleMenuStatus(HdmiCecMessage message) {
   1883         // Do nothing and just return true not to prevent from responding <Feature Abort>.
   1884         return true;
   1885     }
   1886 
   1887     @Override
   1888     protected void sendStandby(int deviceId) {
   1889         HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
   1890         if (targetDevice == null) {
   1891             return;
   1892         }
   1893         int targetAddress = targetDevice.getLogicalAddress();
   1894         mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
   1895     }
   1896 
   1897     @ServiceThreadOnly
   1898     void processAllDelayedMessages() {
   1899         assertRunOnServiceThread();
   1900         mDelayedMessageBuffer.processAllMessages();
   1901     }
   1902 
   1903     @ServiceThreadOnly
   1904     void processDelayedMessages(int address) {
   1905         assertRunOnServiceThread();
   1906         mDelayedMessageBuffer.processMessagesForDevice(address);
   1907     }
   1908 
   1909     @ServiceThreadOnly
   1910     void processDelayedActiveSource(int address) {
   1911         assertRunOnServiceThread();
   1912         mDelayedMessageBuffer.processActiveSource(address);
   1913     }
   1914 
   1915     @Override
   1916     protected void dump(final IndentingPrintWriter pw) {
   1917         super.dump(pw);
   1918         pw.println("mArcEstablished: " + mArcEstablished);
   1919         pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
   1920         pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
   1921         pw.println("mSystemAudioMute: " + mSystemAudioMute);
   1922         pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
   1923         pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
   1924         pw.println("mAutoWakeup: " + mAutoWakeup);
   1925         pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
   1926         pw.println("mPrevPortId: " + mPrevPortId);
   1927         pw.println("CEC devices:");
   1928         pw.increaseIndent();
   1929         for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
   1930             pw.println(info);
   1931         }
   1932         pw.decreaseIndent();
   1933     }
   1934 }
   1935