/*
|
* Copyright (C) 2014 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.server.hdmi;
|
|
import android.hardware.hdmi.HdmiDeviceInfo;
|
import android.util.Slog;
|
|
import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
|
|
import java.util.BitSet;
|
import java.util.List;
|
|
/**
|
* Feature action that handles hot-plug detection mechanism.
|
* Hot-plug event is initiated by timer after device discovery action.
|
*
|
* <p>Check all devices every 15 secs except for system audio.
|
* If system audio is on, check hot-plug for audio system every 5 secs.
|
* For other devices, keep 15 secs period.
|
*/
|
// Seq #3
|
final class HotplugDetectionAction extends HdmiCecFeatureAction {
|
private static final String TAG = "HotPlugDetectionAction";
|
|
private static final int POLLING_INTERVAL_MS = 5000;
|
private static final int TIMEOUT_COUNT = 3;
|
private static final int AVR_COUNT_MAX = 3;
|
|
// State in which waits for next polling
|
private static final int STATE_WAIT_FOR_NEXT_POLLING = 1;
|
|
// All addresses except for broadcast (unregistered address).
|
private static final int NUM_OF_ADDRESS = Constants.ADDR_SPECIFIC_USE
|
- Constants.ADDR_TV + 1;
|
|
private int mTimeoutCount = 0;
|
|
// Counter used to ensure the connection to AVR is stable. Occasional failure to get
|
// polling response from AVR despite its presence leads to unstable status flipping.
|
// This is a workaround to deal with it, by removing the device only if the removal
|
// is detected {@code AVR_COUNT_MAX} times in a row.
|
private int mAvrStatusCount = 0;
|
|
/**
|
* Constructor
|
*
|
* @param source {@link HdmiCecLocalDevice} instance
|
*/
|
HotplugDetectionAction(HdmiCecLocalDevice source) {
|
super(source);
|
}
|
|
@Override
|
boolean start() {
|
Slog.v(TAG, "Hot-plug dection started.");
|
|
mState = STATE_WAIT_FOR_NEXT_POLLING;
|
mTimeoutCount = 0;
|
|
// Start timer without polling.
|
// The first check for all devices will be initiated 15 seconds later.
|
addTimer(mState, POLLING_INTERVAL_MS);
|
return true;
|
}
|
|
@Override
|
boolean processCommand(HdmiCecMessage cmd) {
|
// No-op
|
return false;
|
}
|
|
@Override
|
void handleTimerEvent(int state) {
|
if (mState != state) {
|
return;
|
}
|
|
if (mState == STATE_WAIT_FOR_NEXT_POLLING) {
|
mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT;
|
pollDevices();
|
}
|
}
|
|
/**
|
* Start device polling immediately.
|
*/
|
void pollAllDevicesNow() {
|
// Clear existing timer to avoid overlapped execution
|
mActionTimer.clearTimerMessage();
|
|
mTimeoutCount = 0;
|
mState = STATE_WAIT_FOR_NEXT_POLLING;
|
pollAllDevices();
|
|
addTimer(mState, POLLING_INTERVAL_MS);
|
}
|
|
// This method is called every 5 seconds.
|
private void pollDevices() {
|
// All device check called every 15 seconds.
|
if (mTimeoutCount == 0) {
|
pollAllDevices();
|
} else {
|
if (tv().isSystemAudioActivated()) {
|
pollAudioSystem();
|
}
|
}
|
|
addTimer(mState, POLLING_INTERVAL_MS);
|
}
|
|
private void pollAllDevices() {
|
Slog.v(TAG, "Poll all devices.");
|
|
pollDevices(new DevicePollingCallback() {
|
@Override
|
public void onPollingFinished(List<Integer> ackedAddress) {
|
checkHotplug(ackedAddress, false);
|
}
|
}, Constants.POLL_ITERATION_IN_ORDER
|
| Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.HOTPLUG_DETECTION_RETRY);
|
}
|
|
private void pollAudioSystem() {
|
Slog.v(TAG, "Poll audio system.");
|
|
pollDevices(new DevicePollingCallback() {
|
@Override
|
public void onPollingFinished(List<Integer> ackedAddress) {
|
checkHotplug(ackedAddress, true);
|
}
|
}, Constants.POLL_ITERATION_IN_ORDER
|
| Constants.POLL_STRATEGY_SYSTEM_AUDIO, HdmiConfig.HOTPLUG_DETECTION_RETRY);
|
}
|
|
private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
|
BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly);
|
BitSet polledResult = addressListToBitSet(ackedAddress);
|
|
// At first, check removed devices.
|
BitSet removed = complement(currentInfos, polledResult);
|
int index = -1;
|
while ((index = removed.nextSetBit(index + 1)) != -1) {
|
if (index == Constants.ADDR_AUDIO_SYSTEM) {
|
HdmiDeviceInfo avr = tv().getAvrDeviceInfo();
|
if (avr != null && tv().isConnected(avr.getPortId())) {
|
++mAvrStatusCount;
|
Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount);
|
if (mAvrStatusCount < AVR_COUNT_MAX) {
|
continue;
|
}
|
}
|
}
|
Slog.v(TAG, "Remove device by hot-plug detection:" + index);
|
removeDevice(index);
|
}
|
|
// Reset the counter if the ack is returned from AVR.
|
if (!removed.get(Constants.ADDR_AUDIO_SYSTEM)) {
|
mAvrStatusCount = 0;
|
}
|
|
// Next, check added devices.
|
BitSet added = complement(polledResult, currentInfos);
|
index = -1;
|
while ((index = added.nextSetBit(index + 1)) != -1) {
|
Slog.v(TAG, "Add device by hot-plug detection:" + index);
|
addDevice(index);
|
}
|
}
|
|
private static BitSet infoListToBitSet(List<HdmiDeviceInfo> infoList, boolean audioOnly) {
|
BitSet set = new BitSet(NUM_OF_ADDRESS);
|
for (HdmiDeviceInfo info : infoList) {
|
if (audioOnly) {
|
if (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
|
set.set(info.getLogicalAddress());
|
}
|
} else {
|
set.set(info.getLogicalAddress());
|
}
|
}
|
return set;
|
}
|
|
private static BitSet addressListToBitSet(List<Integer> list) {
|
BitSet set = new BitSet(NUM_OF_ADDRESS);
|
for (Integer value : list) {
|
set.set(value);
|
}
|
return set;
|
}
|
|
// A - B = A & ~B
|
private static BitSet complement(BitSet first, BitSet second) {
|
// Need to clone it so that it doesn't touch original set.
|
BitSet clone = (BitSet) first.clone();
|
clone.andNot(second);
|
return clone;
|
}
|
|
private void addDevice(int addedAddress) {
|
// Sending <Give Physical Address> will initiate new device action.
|
sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(),
|
addedAddress));
|
}
|
|
private void removeDevice(int removedAddress) {
|
mayChangeRoutingPath(removedAddress);
|
mayCancelDeviceSelect(removedAddress);
|
mayCancelOneTouchRecord(removedAddress);
|
mayDisableSystemAudioAndARC(removedAddress);
|
|
tv().removeCecDevice(removedAddress);
|
}
|
|
private void mayChangeRoutingPath(int address) {
|
HdmiDeviceInfo info = tv().getCecDeviceInfo(address);
|
if (info != null) {
|
tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress());
|
}
|
}
|
|
private void mayCancelDeviceSelect(int address) {
|
List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class);
|
if (actions.isEmpty()) {
|
return;
|
}
|
|
// Should have only one Device Select Action
|
DeviceSelectAction action = actions.get(0);
|
if (action.getTargetAddress() == address) {
|
removeAction(DeviceSelectAction.class);
|
}
|
}
|
|
private void mayCancelOneTouchRecord(int address) {
|
List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
|
for (OneTouchRecordAction action : actions) {
|
if (action.getRecorderAddress() == address) {
|
removeAction(action);
|
}
|
}
|
}
|
|
private void mayDisableSystemAudioAndARC(int address) {
|
if (HdmiUtils.getTypeFromAddress(address) != HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
|
return;
|
}
|
|
tv().setSystemAudioMode(false);
|
if (tv().isArcEstablished()) {
|
tv().enableAudioReturnChannel(false);
|
addAndStartAction(new RequestArcTerminationAction(localDevice(), address));
|
}
|
}
|
}
|