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