HdmiCecLocalDeviceTv.java revision c7eba0f1db8928ca779933a564a06989e22a8532
1006c792d99859341201d19f2b86886cc6ee525b4Mathias Agopian/* 2412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Copyright (C) 2014 The Android Open Source Project 3412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * 4412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Licensed under the Apache License, Version 2.0 (the "License"); 5412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * you may not use this file except in compliance with the License. 6412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * You may obtain a copy of the License at 7412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * 8412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * http://www.apache.org/licenses/LICENSE-2.0 9412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * 10412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Unless required by applicable law or agreed to in writing, software 11412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * distributed under the License is distributed on an "AS IS" BASIS, 12412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * See the License for the specific language governing permissions and 14412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * limitations under the License. 15412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich */ 16412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 17412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichpackage com.android.server.hdmi; 18412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 19412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.content.Intent; 20412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.hardware.hdmi.HdmiCecDeviceInfo; 21412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.hardware.hdmi.HdmiControlManager; 22412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.hardware.hdmi.IHdmiControlCallback; 23412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.media.AudioSystem; 24412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.os.IBinder; 25412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.os.RemoteException; 26412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.os.UserHandle; 27412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.util.Slog; 28412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport android.util.SparseArray; 29412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 30412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport com.android.internal.annotations.GuardedBy; 31412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 32412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 33412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 34412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport java.util.ArrayList; 35412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport java.util.Collections; 36412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport java.util.List; 37412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichimport java.util.Locale; 38412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 39412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich/** 40412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Represent a logical device of type TV residing in Android system. 41412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich */ 42412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevichfinal class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 43412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private static final String TAG = "HdmiCecLocalDeviceTv"; 44412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 45412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Whether ARC is available or not. "true" means that ARC is estabilished between TV and 46412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // AVR as audio receiver. 47412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @ServiceThreadOnly 48412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private boolean mArcEstablished = false; 49412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 50412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Whether ARC feature is enabled or not. 51412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private boolean mArcFeatureEnabled = false; 52412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 53412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Whether SystemAudioMode is "On" or not. 54412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @GuardedBy("mLock") 55412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private boolean mSystemAudioMode; 56412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 57412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // The previous port id (input) before switching to the new one. This is remembered in order to 58412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // be able to switch to it upon receiving <Inactive Source> from currently active source. 59412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // This remains valid only when the active source was switched via one touch play operation 60412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // (either by TV or source device). Manual port switching invalidates this value to 61412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything. 62412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @GuardedBy("mLock") 63412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private int mPrevPortId; 64412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 65412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @GuardedBy("mLock") 66412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME; 67412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 68412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @GuardedBy("mLock") 69412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private boolean mSystemAudioMute = false; 70412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 71412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Copy of mDeviceInfos to guarantee thread-safety. 72412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @GuardedBy("mLock") 73412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private List<HdmiCecDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList(); 74412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // All external cec input(source) devices. Does not include system audio device. 75412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @GuardedBy("mLock") 76412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private List<HdmiCecDeviceInfo> mSafeExternalInputs = Collections.emptyList(); 77412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 78412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Map-like container of all cec devices including local ones. 79412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // A logical address of device is used as key of container. 80412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // This is not thread-safe. For external purpose use mSafeDeviceInfos. 81412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = new SparseArray<>(); 82412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 83412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // If true, TV going to standby mode puts other devices also to standby. 84412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private boolean mAutoDeviceOff; 85412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 86412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich HdmiCecLocalDeviceTv(HdmiControlService service) { 87412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich super(service, HdmiCecDeviceInfo.DEVICE_TV); 88412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich mPrevPortId = Constants.INVALID_PORT_ID; 89412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // TODO: load system audio mode and set it to mSystemAudioMode. 90412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 91412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 92412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @Override 93412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @ServiceThreadOnly 94412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich protected void onAddressAllocated(int logicalAddress) { 95412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich assertRunOnServiceThread(); 96412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // TODO: vendor-specific initialization here. 97412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 98412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 99412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich mAddress, mService.getPhysicalAddress(), mDeviceType)); 100412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 101412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich mAddress, mService.getVendorId())); 102412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich launchRoutingControl(true); 103412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich launchDeviceDiscovery(); 104412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 105412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 106412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich /** 107412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Performs the action 'device select', or 'one touch play' initiated by TV. 108412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * 109412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * @param targetAddress logical address of the device to select 110412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * @param callback callback object to report the result with 111412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich */ 112412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @ServiceThreadOnly 113412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich void deviceSelect(int targetAddress, IHdmiControlCallback callback) { 114412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich assertRunOnServiceThread(); 115412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (targetAddress == Constants.ADDR_INTERNAL) { 116412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich handleSelectInternalSource(callback); 117412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich return; 118412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 119412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress); 120412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (targetDevice == null) { 121412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 122412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich return; 123412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 124412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich removeAction(DeviceSelectAction.class); 125412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich addAndStartAction(new DeviceSelectAction(this, targetDevice, callback)); 126412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 127412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 128412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @ServiceThreadOnly 129412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private void handleSelectInternalSource(IHdmiControlCallback callback) { 130412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich assertRunOnServiceThread(); 131412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Seq #18 132412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (mService.isControlEnabled() && getActiveSource() != mAddress) { 133412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich updateActiveSource(mAddress, mService.getPhysicalAddress()); 134412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // TODO: Check if this comes from <Text/Image View On> - if true, do nothing. 135412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( 136412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich mAddress, mService.getPhysicalAddress()); 137412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich mService.sendCecCommand(activeSource); 138412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 139412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 140412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 141412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @ServiceThreadOnly 142412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich void updateActiveSource(int activeSource, int activePath) { 143412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich assertRunOnServiceThread(); 144412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Seq #14 145412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (activeSource == getActiveSource() && activePath == getActivePath()) { 146412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich return; 147412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 148412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich setActiveSource(activeSource); 149412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich setActivePath(activePath); 150412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (getDeviceInfo(activeSource) != null && activeSource != mAddress) { 151412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (mService.pathToPortId(activePath) == getActivePortId()) { 152412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich setPrevPortId(getActivePortId()); 153412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 154412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // TODO: Show the OSD banner related to the new active source device. 155412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } else { 156412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // TODO: If displayed, remove the OSD banner related to the previous 157412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // active source device. 158412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 159412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 160412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 161412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich /** 162412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Returns the previous port id kept to handle input switching on <Inactive Source>. 163412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich */ 164412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich int getPrevPortId() { 165412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich synchronized (mLock) { 166412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich return mPrevPortId; 167412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 168412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 169412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 170412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich /** 171412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be 172412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * taken for <Inactive Source>. 173412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich */ 174412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich void setPrevPortId(int portId) { 175412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich synchronized (mLock) { 176412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich mPrevPortId = portId; 177412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 178412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 179412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 180412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @ServiceThreadOnly 181412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich void updateActivePortId(int portId) { 182412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich assertRunOnServiceThread(); 183412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Seq #15 184412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (portId == getActivePortId()) { 185412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich return; 186412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 187412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich setPrevPortId(portId); 188412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // TODO: Actually switch the physical port here. Handle PAP/PIP as well. 189412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Show OSD port change banner 190412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich mService.invokeInputChangeListener(getActiveSource()); 191412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 192412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 193412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @ServiceThreadOnly 194412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich void doManualPortSwitching(int portId, IHdmiControlCallback callback) { 195412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich assertRunOnServiceThread(); 196412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // Seq #20 197412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (!mService.isControlEnabled() || portId == getActivePortId()) { 198412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 199412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich return; 200412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 201412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // TODO: Make sure this call does not stem from <Active Source> message reception. 202412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 203412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich setActivePortId(portId); 204412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // TODO: Return immediately if the operation is triggered by <Text/Image View On> 205412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // and this is the first notification about the active input after power-on. 206412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // TODO: Handle invalid port id / active input which should be treated as an 207412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich // internal tuner. 208412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 209412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich removeAction(RoutingControlAction.class); 210412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 211412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId())); 212412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich int newPath = mService.portIdToPath(portId); 213412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich HdmiCecMessage routingChange = 214412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); 215412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich mService.sendCecCommand(routingChange); 216412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich addAndStartAction(new RoutingControlAction(this, newPath, false, callback)); 217412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 218412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 219412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich int getPowerStatus() { 220412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich return mService.getPowerStatus(); 221412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 222412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 223412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich /** 224412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * Sends key to a target CEC device. 225412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * 226412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. 227412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich * @param isPressed true if this is keypress event 228412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich */ 229412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @ServiceThreadOnly 230412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich void sendKeyEvent(int keyCode, boolean isPressed) { 231412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich assertRunOnServiceThread(); 232412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich List<SendKeyAction> action = getActions(SendKeyAction.class); 233412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (!action.isEmpty()) { 234412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich action.get(0).processKeyEvent(keyCode, isPressed); 235412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } else { 236412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (isPressed) { 237412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich addAndStartAction(new SendKeyAction(this, getActiveSource(), keyCode)); 238412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } else { 239412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich Slog.w(TAG, "Discard key release event"); 240412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 241412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 242412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 243412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 244412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich private static void invokeCallback(IHdmiControlCallback callback, int result) { 245412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (callback == null) { 246412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich return; 247412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 248412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich try { 249412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich callback.onComplete(result); 250412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } catch (RemoteException e) { 251412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich Slog.e(TAG, "Invoking callback failed:" + e); 252412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 253412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 254412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 255412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @Override 256412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @ServiceThreadOnly 257412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich protected boolean handleActiveSource(HdmiCecMessage message) { 258412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich assertRunOnServiceThread(); 259412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich int address = message.getSource(); 260412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich int path = HdmiUtils.twoBytesToInt(message.getParams()); 261412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich if (getDeviceInfo(address) == null) { 262412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich handleNewDeviceAtTheTailOfActivePath(address, path); 263412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } else { 264412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich ActiveSourceHandler.create(this, null).process(address, path); 2652fc72d1c0294fb759380604ea906ce67ebeb5c5fJesse Hall } 266412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich return true; 267412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich } 268412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich 269412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @Override 270412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich @ServiceThreadOnly 271412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich protected boolean handleInactiveSource(HdmiCecMessage message) { 272412f38f270b71bbc3214362eb58687cbf26d6df2Jack Palevich assertRunOnServiceThread(); 273 // Seq #10 274 275 // Ignore <Inactive Source> from non-active source device. 276 if (getActiveSource() != message.getSource()) { 277 return true; 278 } 279 if (isProhibitMode()) { 280 return true; 281 } 282 int portId = getPrevPortId(); 283 if (portId != Constants.INVALID_PORT_ID) { 284 // TODO: Do this only if TV is not showing multiview like PIP/PAP. 285 286 HdmiCecDeviceInfo inactiveSource = getDeviceInfo(message.getSource()); 287 if (inactiveSource == null) { 288 return true; 289 } 290 if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) { 291 return true; 292 } 293 // TODO: Switch the TV freeze mode off 294 295 setActivePortId(portId); 296 doManualPortSwitching(portId, null); 297 setPrevPortId(Constants.INVALID_PORT_ID); 298 } 299 return true; 300 } 301 302 @Override 303 @ServiceThreadOnly 304 protected boolean handleRequestActiveSource(HdmiCecMessage message) { 305 assertRunOnServiceThread(); 306 // Seq #19 307 if (mAddress == getActiveSource()) { 308 mService.sendCecCommand( 309 HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath())); 310 } 311 return true; 312 } 313 314 @Override 315 @ServiceThreadOnly 316 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 317 assertRunOnServiceThread(); 318 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 319 mAddress, Locale.getDefault().getISO3Language()); 320 // TODO: figure out how to handle failed to get language code. 321 if (command != null) { 322 mService.sendCecCommand(command); 323 } else { 324 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 325 } 326 return true; 327 } 328 329 @Override 330 @ServiceThreadOnly 331 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 332 assertRunOnServiceThread(); 333 // Ignore if [Device Discovery Action] is going on. 334 if (hasAction(DeviceDiscoveryAction.class)) { 335 Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> " 336 + "because Device Discovery Action is on-going:" + message); 337 return true; 338 } 339 340 int path = HdmiUtils.twoBytesToInt(message.getParams()); 341 int address = message.getSource(); 342 if (!isInDeviceList(path, address)) { 343 handleNewDeviceAtTheTailOfActivePath(address, path); 344 } 345 addAndStartAction(new NewDeviceAction(this, address, path)); 346 return true; 347 } 348 349 private void handleNewDeviceAtTheTailOfActivePath(int address, int path) { 350 // Seq #22 351 if (isTailOfActivePath(path, getActivePath())) { 352 removeAction(RoutingControlAction.class); 353 int newPath = mService.portIdToPath(getActivePortId()); 354 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange( 355 mAddress, getActivePath(), newPath)); 356 addAndStartAction(new RoutingControlAction(this, getActivePortId(), false, null)); 357 } 358 } 359 360 /** 361 * Whether the given path is located in the tail of current active path. 362 * 363 * @param path to be tested 364 * @param activePath current active path 365 * @return true if the given path is located in the tail of current active path; otherwise, 366 * false 367 */ 368 static boolean isTailOfActivePath(int path, int activePath) { 369 // If active routing path is internal source, return false. 370 if (activePath == 0) { 371 return false; 372 } 373 for (int i = 12; i >= 0; i -= 4) { 374 int curActivePath = (activePath >> i) & 0xF; 375 if (curActivePath == 0) { 376 return true; 377 } else { 378 int curPath = (path >> i) & 0xF; 379 if (curPath != curActivePath) { 380 return false; 381 } 382 } 383 } 384 return false; 385 } 386 387 @Override 388 @ServiceThreadOnly 389 protected boolean handleRoutingChange(HdmiCecMessage message) { 390 assertRunOnServiceThread(); 391 // Seq #21 392 byte[] params = message.getParams(); 393 if (params.length != 4) { 394 Slog.w(TAG, "Wrong parameter: " + message); 395 return true; 396 } 397 int currentPath = HdmiUtils.twoBytesToInt(params); 398 if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) { 399 int newPath = HdmiUtils.twoBytesToInt(params, 2); 400 setActivePath(newPath); 401 removeAction(RoutingControlAction.class); 402 addAndStartAction(new RoutingControlAction(this, newPath, true, null)); 403 } 404 return true; 405 } 406 407 @Override 408 @ServiceThreadOnly 409 protected boolean handleReportAudioStatus(HdmiCecMessage message) { 410 assertRunOnServiceThread(); 411 412 byte params[] = message.getParams(); 413 if (params.length < 1) { 414 Slog.w(TAG, "Invalide <Report Audio Status> message:" + message); 415 return true; 416 } 417 int mute = params[0] & 0x80; 418 int volume = params[0] & 0x7F; 419 setAudioStatus(mute == 0x80, volume); 420 return true; 421 } 422 423 @Override 424 @ServiceThreadOnly 425 protected boolean handleTextViewOn(HdmiCecMessage message) { 426 assertRunOnServiceThread(); 427 if (mService.isPowerStandbyOrTransient()) { 428 mService.wakeUp(); 429 } 430 // TODO: Connect to Hardware input manager to invoke TV App with the appropriate channel 431 // that represents the source device. 432 return true; 433 } 434 435 @Override 436 @ServiceThreadOnly 437 protected boolean handleImageViewOn(HdmiCecMessage message) { 438 assertRunOnServiceThread(); 439 // Currently, it's the same as <Text View On>. 440 return handleTextViewOn(message); 441 } 442 443 @ServiceThreadOnly 444 private void launchDeviceDiscovery() { 445 assertRunOnServiceThread(); 446 clearDeviceInfoList(); 447 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 448 new DeviceDiscoveryCallback() { 449 @Override 450 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 451 for (HdmiCecDeviceInfo info : deviceInfos) { 452 addCecDevice(info); 453 } 454 455 // Since we removed all devices when it's start and 456 // device discovery action does not poll local devices, 457 // we should put device info of local device manually here 458 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 459 addCecDevice(device.getDeviceInfo()); 460 } 461 462 addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); 463 464 // If there is AVR, initiate System Audio Auto initiation action, 465 // which turns on and off system audio according to last system 466 // audio setting. 467 HdmiCecDeviceInfo avrInfo = getAvrDeviceInfo(); 468 if (avrInfo != null) { 469 addAndStartAction(new SystemAudioAutoInitiationAction( 470 HdmiCecLocalDeviceTv.this, avrInfo.getLogicalAddress())); 471 if (mArcEstablished) { 472 startArcAction(true); 473 } 474 } 475 } 476 }); 477 addAndStartAction(action); 478 } 479 480 // Clear all device info. 481 @ServiceThreadOnly 482 private void clearDeviceInfoList() { 483 assertRunOnServiceThread(); 484 mDeviceInfos.clear(); 485 updateSafeDeviceInfoList(); 486 } 487 488 @ServiceThreadOnly 489 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { 490 assertRunOnServiceThread(); 491 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 492 if (avr == null) { 493 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 494 return; 495 } 496 497 addAndStartAction( 498 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback)); 499 } 500 501 // # Seq 25 502 void setSystemAudioMode(boolean on) { 503 synchronized (mLock) { 504 if (on != mSystemAudioMode) { 505 mSystemAudioMode = on; 506 // TODO: Need to set the preference for SystemAudioMode. 507 mService.announceSystemAudioModeChange(on); 508 } 509 } 510 } 511 512 boolean getSystemAudioMode() { 513 synchronized (mLock) { 514 return mSystemAudioMode; 515 } 516 } 517 518 /** 519 * Change ARC status into the given {@code enabled} status. 520 * 521 * @return {@code true} if ARC was in "Enabled" status 522 */ 523 @ServiceThreadOnly 524 boolean setArcStatus(boolean enabled) { 525 assertRunOnServiceThread(); 526 boolean oldStatus = mArcEstablished; 527 // 1. Enable/disable ARC circuit. 528 mService.setAudioReturnChannel(enabled); 529 // 2. Notify arc status to audio service. 530 notifyArcStatusToAudioService(enabled); 531 // 3. Update arc status; 532 mArcEstablished = enabled; 533 return oldStatus; 534 } 535 536 private void notifyArcStatusToAudioService(boolean enabled) { 537 // Note that we don't set any name to ARC. 538 mService.getAudioManager().setWiredDeviceConnectionState( 539 AudioSystem.DEVICE_OUT_HDMI_ARC, 540 enabled ? 1 : 0, ""); 541 } 542 543 /** 544 * Returns whether ARC is enabled or not. 545 */ 546 @ServiceThreadOnly 547 boolean isArcEstabilished() { 548 assertRunOnServiceThread(); 549 return mArcFeatureEnabled && mArcEstablished; 550 } 551 552 @ServiceThreadOnly 553 void changeArcFeatureEnabled(boolean enabled) { 554 assertRunOnServiceThread(); 555 556 if (mArcFeatureEnabled != enabled) { 557 if (enabled) { 558 if (!mArcEstablished) { 559 startArcAction(true); 560 } 561 } else { 562 if (mArcEstablished) { 563 startArcAction(false); 564 } 565 } 566 mArcFeatureEnabled = enabled; 567 } 568 } 569 570 @ServiceThreadOnly 571 boolean isArcFeatureEnabled() { 572 assertRunOnServiceThread(); 573 return mArcFeatureEnabled; 574 } 575 576 @ServiceThreadOnly 577 private void startArcAction(boolean enabled) { 578 assertRunOnServiceThread(); 579 HdmiCecDeviceInfo info = getAvrDeviceInfo(); 580 if (info == null) { 581 return; 582 } 583 if (!isConnectedToArcPort(info.getPhysicalAddress())) { 584 return; 585 } 586 587 // Terminate opposite action and start action if not exist. 588 if (enabled) { 589 removeAction(RequestArcTerminationAction.class); 590 if (!hasAction(RequestArcInitiationAction.class)) { 591 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress())); 592 } 593 } else { 594 removeAction(RequestArcInitiationAction.class); 595 if (!hasAction(RequestArcTerminationAction.class)) { 596 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress())); 597 } 598 } 599 } 600 601 void setAudioStatus(boolean mute, int volume) { 602 synchronized (mLock) { 603 mSystemAudioMute = mute; 604 mSystemAudioVolume = volume; 605 // TODO: pass volume to service (audio service) after scale it to local volume level. 606 mService.setAudioStatus(mute, volume); 607 } 608 } 609 610 @ServiceThreadOnly 611 void changeVolume(int curVolume, int delta, int maxVolume) { 612 assertRunOnServiceThread(); 613 if (delta == 0 || !isSystemAudioOn()) { 614 return; 615 } 616 617 int targetVolume = curVolume + delta; 618 int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume); 619 synchronized (mLock) { 620 // If new volume is the same as current system audio volume, just ignore it. 621 // Note that UNKNOWN_VOLUME is not in range of cec volume scale. 622 if (cecVolume == mSystemAudioVolume) { 623 // Update tv volume with system volume value. 624 mService.setAudioStatus(false, 625 VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume)); 626 return; 627 } 628 } 629 630 // Remove existing volume action. 631 removeAction(VolumeControlAction.class); 632 633 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 634 addAndStartAction(VolumeControlAction.ofVolumeChange(this, avr.getLogicalAddress(), 635 cecVolume, delta > 0)); 636 } 637 638 @ServiceThreadOnly 639 void changeMute(boolean mute) { 640 assertRunOnServiceThread(); 641 if (!isSystemAudioOn()) { 642 return; 643 } 644 645 // Remove existing volume action. 646 removeAction(VolumeControlAction.class); 647 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 648 addAndStartAction(VolumeControlAction.ofMute(this, avr.getLogicalAddress(), mute)); 649 } 650 651 private boolean isSystemAudioOn() { 652 if (getAvrDeviceInfo() == null) { 653 return false; 654 } 655 656 synchronized (mLock) { 657 return mSystemAudioMode; 658 } 659 } 660 661 @Override 662 @ServiceThreadOnly 663 protected boolean handleInitiateArc(HdmiCecMessage message) { 664 assertRunOnServiceThread(); 665 // In case where <Initiate Arc> is started by <Request ARC Initiation> 666 // need to clean up RequestArcInitiationAction. 667 removeAction(RequestArcInitiationAction.class); 668 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 669 message.getSource(), true); 670 addAndStartAction(action); 671 return true; 672 } 673 674 @Override 675 @ServiceThreadOnly 676 protected boolean handleTerminateArc(HdmiCecMessage message) { 677 assertRunOnServiceThread(); 678 // In case where <Terminate Arc> is started by <Request ARC Termination> 679 // need to clean up RequestArcInitiationAction. 680 // TODO: check conditions of power status by calling is_connected api 681 // to be added soon. 682 removeAction(RequestArcTerminationAction.class); 683 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 684 message.getSource(), false); 685 addAndStartAction(action); 686 return true; 687 } 688 689 @Override 690 @ServiceThreadOnly 691 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 692 assertRunOnServiceThread(); 693 if (!isMessageForSystemAudio(message)) { 694 return false; 695 } 696 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 697 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null); 698 addAndStartAction(action); 699 return true; 700 } 701 702 @Override 703 @ServiceThreadOnly 704 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 705 assertRunOnServiceThread(); 706 if (!isMessageForSystemAudio(message)) { 707 return false; 708 } 709 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 710 return true; 711 } 712 713 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 714 if (message.getSource() != Constants.ADDR_AUDIO_SYSTEM 715 || message.getDestination() != Constants.ADDR_TV 716 || getAvrDeviceInfo() == null) { 717 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 718 return false; 719 } 720 return true; 721 } 722 723 /** 724 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same 725 * logical address as new device info's. 726 * 727 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 728 * 729 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added. 730 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo} 731 * that has the same logical address as new one has. 732 */ 733 @ServiceThreadOnly 734 HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 735 assertRunOnServiceThread(); 736 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress()); 737 if (oldDeviceInfo != null) { 738 removeDeviceInfo(deviceInfo.getLogicalAddress()); 739 } 740 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo); 741 updateSafeDeviceInfoList(); 742 return oldDeviceInfo; 743 } 744 745 /** 746 * Remove a device info corresponding to the given {@code logicalAddress}. 747 * It returns removed {@link HdmiCecDeviceInfo} if exists. 748 * 749 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 750 * 751 * @param logicalAddress logical address of device to be removed 752 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null} 753 */ 754 @ServiceThreadOnly 755 HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) { 756 assertRunOnServiceThread(); 757 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress); 758 if (deviceInfo != null) { 759 mDeviceInfos.remove(logicalAddress); 760 } 761 updateSafeDeviceInfoList(); 762 return deviceInfo; 763 } 764 765 /** 766 * Return a list of all {@link HdmiCecDeviceInfo}. 767 * 768 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 769 * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfoList(boolean)}. 770 */ 771 @ServiceThreadOnly 772 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includelLocalDevice) { 773 assertRunOnServiceThread(); 774 if (includelLocalDevice) { 775 return HdmiUtils.sparseArrayToList(mDeviceInfos); 776 } else { 777 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>(); 778 for (int i = 0; i < mDeviceInfos.size(); ++i) { 779 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i); 780 if (!isLocalDeviceAddress(info.getLogicalAddress())) { 781 infoList.add(info); 782 } 783 } 784 return infoList; 785 } 786 } 787 788 /** 789 * Return external input devices. 790 */ 791 List<HdmiCecDeviceInfo> getSafeExternalInputs() { 792 synchronized (mLock) { 793 return mSafeExternalInputs; 794 } 795 } 796 797 @ServiceThreadOnly 798 private void updateSafeDeviceInfoList() { 799 assertRunOnServiceThread(); 800 List<HdmiCecDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); 801 List<HdmiCecDeviceInfo> externalInputs = getInputDevices(); 802 synchronized (mLock) { 803 mSafeAllDeviceInfos = copiedDevices; 804 mSafeExternalInputs = externalInputs; 805 } 806 } 807 808 /** 809 * Return a list of external cec input (source) devices. 810 * 811 * <p>Note that this effectively excludes non-source devices like system audio, 812 * secondary TV. 813 */ 814 private List<HdmiCecDeviceInfo> getInputDevices() { 815 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>(); 816 for (int i = 0; i < mDeviceInfos.size(); ++i) { 817 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i); 818 if (isLocalDeviceAddress(i)) { 819 continue; 820 } 821 if (info.isSourceType()) { 822 infoList.add(info); 823 } 824 } 825 return infoList; 826 } 827 828 @ServiceThreadOnly 829 private boolean isLocalDeviceAddress(int address) { 830 assertRunOnServiceThread(); 831 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 832 if (device.isAddressOf(address)) { 833 return true; 834 } 835 } 836 return false; 837 } 838 839 @ServiceThreadOnly 840 HdmiCecDeviceInfo getAvrDeviceInfo() { 841 assertRunOnServiceThread(); 842 return getDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 843 } 844 845 /** 846 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}. 847 * 848 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 849 * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfo(int)}. 850 * 851 * @param logicalAddress logical address to be retrieved 852 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 853 * Returns null if no logical address matched 854 */ 855 @ServiceThreadOnly 856 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 857 assertRunOnServiceThread(); 858 return mDeviceInfos.get(logicalAddress); 859 } 860 861 boolean hasSystemAudioDevice() { 862 return getSafeAvrDeviceInfo() != null; 863 } 864 865 HdmiCecDeviceInfo getSafeAvrDeviceInfo() { 866 return getSafeDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 867 } 868 869 /** 870 * Thread safe version of {@link #getDeviceInfo(int)}. 871 * 872 * @param logicalAddress logical address to be retrieved 873 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 874 * Returns null if no logical address matched 875 */ 876 HdmiCecDeviceInfo getSafeDeviceInfo(int logicalAddress) { 877 synchronized (mLock) { 878 return mSafeAllDeviceInfos.get(logicalAddress); 879 } 880 } 881 882 /** 883 * Called when a device is newly added or a new device is detected. 884 * 885 * @param info device info of a new device. 886 */ 887 @ServiceThreadOnly 888 final void addCecDevice(HdmiCecDeviceInfo info) { 889 assertRunOnServiceThread(); 890 addDeviceInfo(info); 891 if (info.getLogicalAddress() == mAddress) { 892 // The addition of TV device itself should not be notified. 893 return; 894 } 895 mService.invokeDeviceEventListeners(info, true); 896 } 897 898 /** 899 * Called when a device is removed or removal of device is detected. 900 * 901 * @param address a logical address of a device to be removed 902 */ 903 @ServiceThreadOnly 904 final void removeCecDevice(int address) { 905 assertRunOnServiceThread(); 906 HdmiCecDeviceInfo info = removeDeviceInfo(address); 907 908 mCecMessageCache.flushMessagesFrom(address); 909 mService.invokeDeviceEventListeners(info, false); 910 } 911 912 @ServiceThreadOnly 913 void handleRemoveActiveRoutingPath(int path) { 914 assertRunOnServiceThread(); 915 // Seq #23 916 if (isTailOfActivePath(path, getActivePath())) { 917 removeAction(RoutingControlAction.class); 918 int newPath = mService.portIdToPath(getActivePortId()); 919 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange( 920 mAddress, getActivePath(), newPath)); 921 addAndStartAction(new RoutingControlAction(this, getActivePortId(), true, null)); 922 } 923 } 924 925 /** 926 * Launch routing control process. 927 * 928 * @param routingForBootup true if routing control is initiated due to One Touch Play 929 * or TV power on 930 */ 931 @ServiceThreadOnly 932 void launchRoutingControl(boolean routingForBootup) { 933 assertRunOnServiceThread(); 934 // Seq #24 935 if (getActivePortId() != Constants.INVALID_PORT_ID) { 936 if (!routingForBootup && !isProhibitMode()) { 937 removeAction(RoutingControlAction.class); 938 int newPath = mService.portIdToPath(getActivePortId()); 939 setActivePath(newPath); 940 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(mAddress, 941 getActivePath(), newPath)); 942 addAndStartAction(new RoutingControlAction(this, getActivePortId(), 943 routingForBootup, null)); 944 } 945 } else { 946 int activePath = mService.getPhysicalAddress(); 947 setActivePath(activePath); 948 if (!routingForBootup) { 949 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress, 950 activePath)); 951 } 952 } 953 } 954 955 /** 956 * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches 957 * the given routing path. CEC devices use routing path for its physical address to 958 * describe the hierarchy of the devices in the network. 959 * 960 * @param path routing path or physical address 961 * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null 962 */ 963 @ServiceThreadOnly 964 final HdmiCecDeviceInfo getDeviceInfoByPath(int path) { 965 assertRunOnServiceThread(); 966 for (HdmiCecDeviceInfo info : getDeviceInfoList(false)) { 967 if (info.getPhysicalAddress() == path) { 968 return info; 969 } 970 } 971 return null; 972 } 973 974 /** 975 * Whether a device of the specified physical address and logical address exists 976 * in a device info list. However, both are minimal condition and it could 977 * be different device from the original one. 978 * 979 * @param logicalAddress logical address of a device to be searched 980 * @param physicalAddress physical address of a device to be searched 981 * @return true if exist; otherwise false 982 */ 983 @ServiceThreadOnly 984 boolean isInDeviceList(int logicalAddress, int physicalAddress) { 985 assertRunOnServiceThread(); 986 HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress); 987 if (device == null) { 988 return false; 989 } 990 return device.getPhysicalAddress() == physicalAddress; 991 } 992 993 @Override 994 @ServiceThreadOnly 995 void onHotplug(int portId, boolean connected) { 996 assertRunOnServiceThread(); 997 998 // Tv device will have permanent HotplugDetectionAction. 999 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 1000 if (!hotplugActions.isEmpty()) { 1001 // Note that hotplug action is single action running on a machine. 1002 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 1003 hotplugActions.get(0).pollAllDevicesNow(); 1004 } 1005 } 1006 1007 @ServiceThreadOnly 1008 void setAutoDeviceOff(boolean enabled) { 1009 assertRunOnServiceThread(); 1010 mAutoDeviceOff = enabled; 1011 } 1012 1013 @Override 1014 @ServiceThreadOnly 1015 protected void onTransitionToStandby(boolean initiatedByCec) { 1016 assertRunOnServiceThread(); 1017 // Remove any repeated working actions. 1018 // HotplugDetectionAction will be reinstated during the wake up process. 1019 // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> 1020 // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery(). 1021 removeAction(HotplugDetectionAction.class); 1022 checkIfPendingActionsCleared(); 1023 } 1024 1025 @Override 1026 @ServiceThreadOnly 1027 protected void onStandBy(boolean initiatedByCec) { 1028 assertRunOnServiceThread(); 1029 // Seq #11 1030 if (!mService.isControlEnabled()) { 1031 return; 1032 } 1033 if (!initiatedByCec) { 1034 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby( 1035 mAddress, Constants.ADDR_BROADCAST)); 1036 } 1037 } 1038 1039 @Override 1040 @ServiceThreadOnly 1041 protected boolean handleStandby(HdmiCecMessage message) { 1042 assertRunOnServiceThread(); 1043 // Seq #12 1044 // Tv accepts directly addressed <Standby> only. 1045 if (message.getDestination() == mAddress) { 1046 super.handleStandby(message); 1047 } 1048 return false; 1049 } 1050 1051 boolean isProhibitMode() { 1052 return mService.isProhibitMode(); 1053 } 1054 1055 boolean isPowerStandbyOrTransient() { 1056 return mService.isPowerStandbyOrTransient(); 1057 1058 void displayOsd(int messageId) { 1059 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 1060 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 1061 mService.getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 1062 HdmiControlService.PERMISSION); 1063 } 1064} 1065