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