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