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