Hotplug detection is periodic operation to detect new/removed device.
In case of directly connected device fires hotplug devices
but remote devices which are plugged in hdmi switch may not
report their existence.
Hotplug action polls all devices in regular base; 5s for
system audio (only when it's active) 15s for others.
Change-Id: I77c308ecfd5d6bf92f306923fa5ac9eabdad4127
allocateDevices(ackedAddress);
startPhysicalAddressStage();
}
- }, DEVICE_POLLING_RETRY);
+ }, HdmiControlService.POLL_STRATEGY_REMOTES_DEVICES, DEVICE_POLLING_RETRY);
return true;
}
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.util.Predicate;
import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
import libcore.util.EmptyArray;
private static final int RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION = 3;
+ // Predicate for whether the given logical address is remote device's one or not.
+ private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
+ @Override
+ public boolean apply(Integer address) {
+ return !isAllocatedLocalDeviceAddress(address);
+ }
+ };
+
+ // Predicate whether the given logical address is system audio's one or not
+ private final Predicate<Integer> mSystemAudioAddressPredicate = new Predicate<Integer>() {
+ @Override
+ public boolean apply(Integer address) {
+ return HdmiCec.getTypeFromAddress(address) == HdmiCec.ADDR_AUDIO_SYSTEM;
+ }
+ };
+
// Handler instance to process synchronous I/O (mainly send) message.
private Handler mIoHandler;
* Return a list of all {@link HdmiCecDeviceInfo}.
*
* <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @param includeLocalDevice whether to add local device or not
*/
- List<HdmiCecDeviceInfo> getDeviceInfoList() {
+ List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
assertRunOnServiceThread();
- return sparseArrayToList(mDeviceInfos);
+ if (includeLocalDevice) {
+ return sparseArrayToList(mDeviceInfos);
+ } else {
+ ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>();
+ for (int i = 0; i < mDeviceInfos.size(); ++i) {
+ HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i);
+ if (mRemoteDeviceAddressPredicate.apply(info.getLogicalAddress())) {
+ infoList.add(info);
+ }
+ }
+ return infoList;
+ }
}
/**
* <p>Declared as package-private. accessed by {@link HdmiControlService} only.
*
* @param callback an interface used to get a list of all remote devices' address
+ * @param pickStrategy strategy how to pick polling candidates
* @param retryCount the number of retry used to send polling message to remote devices
*/
- void pollDevices(DevicePollingCallback callback, int retryCount) {
+ void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
assertRunOnServiceThread();
- // Extract polling candidates. No need to poll against local devices.
- ArrayList<Integer> pollingCandidates = new ArrayList<>();
- for (int i = HdmiCec.ADDR_SPECIFIC_USE; i >= HdmiCec.ADDR_TV; --i) {
- if (!isAllocatedLocalDeviceAddress(i)) {
- pollingCandidates.add(i);
- }
- }
+ // Extract polling candidates. No need to poll against local devices.
+ List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
runDevicePolling(pollingCandidates, retryCount, callback);
}
return sparseArrayToList(mLocalDevices);
}
+ private List<Integer> pickPollCandidates(int pickStrategy) {
+ int strategy = pickStrategy & HdmiControlService.POLL_STRATEGY_MASK;
+ Predicate<Integer> pickPredicate = null;
+ switch (strategy) {
+ case HdmiControlService.POLL_STRATEGY_SYSTEM_AUDIO:
+ pickPredicate = mSystemAudioAddressPredicate;
+ break;
+ case HdmiControlService.POLL_STRATEGY_REMOTES_DEVICES:
+ default: // The default is POLL_STRATEGY_REMOTES_DEVICES.
+ pickPredicate = mRemoteDeviceAddressPredicate;
+ break;
+ }
+
+ int iterationStrategy = pickStrategy & HdmiControlService.POLL_ITERATION_STRATEGY_MASK;
+ ArrayList<Integer> pollingCandidates = new ArrayList<>();
+ switch (iterationStrategy) {
+ case HdmiControlService.POLL_ITERATION_IN_ORDER:
+ for (int i = HdmiCec.ADDR_TV; i <= HdmiCec.ADDR_SPECIFIC_USE; ++i) {
+ if (pickPredicate.apply(i)) {
+ pollingCandidates.add(i);
+ }
+ }
+ break;
+ case HdmiControlService.POLL_ITERATION_REVERSE_ORDER:
+ default: // The default is reverse order.
+ for (int i = HdmiCec.ADDR_SPECIFIC_USE; i >= HdmiCec.ADDR_TV; --i) {
+ if (pickPredicate.apply(i)) {
+ pollingCandidates.add(i);
+ }
+ }
+ break;
+ }
+ return pollingCandidates;
+ }
+
private static <T> List<T> sparseArrayToList(SparseArray<T> array) {
ArrayList<T> list = new ArrayList<>();
for (int i = 0; i < array.size(); ++i) {
static final int SEND_RESULT_NAK = -1;
static final int SEND_RESULT_FAILURE = -2;
+ static final int POLL_STRATEGY_MASK = 0x3; // first and second bit.
+ static final int POLL_STRATEGY_REMOTES_DEVICES = 0x1;
+ static final int POLL_STRATEGY_SYSTEM_AUDIO = 0x2;
+
+ static final int POLL_ITERATION_STRATEGY_MASK = 0x30000; // first and second bit.
+ static final int POLL_ITERATION_IN_ORDER = 0x10000;
+ static final int POLL_ITERATION_REVERSE_ORDER = 0x20000;
+
/**
* Interface to report send result.
*/
@Nullable
private HdmiMhlController mMhlController;
+ @GuardedBy("mLock")
// Whether ARC is "enabled" or not.
// TODO: it may need to hold lock if it's accessed from others.
private boolean mArcStatusEnabled = false;
+ @GuardedBy("mLock")
// Whether SystemAudioMode is "On" or not.
private boolean mSystemAudioMode;
}
/**
+ * Returns a list of {@link HdmiCecDeviceInfo}.
+ *
+ * @param includeLocalDevice whether to include local devices
+ */
+ List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
+ assertRunOnServiceThread();
+ return mCecController.getDeviceInfoList(includeLocalDevice);
+ }
+
+ /**
* Add and start a new {@link FeatureAction} to the action queue.
*
* @param action {@link FeatureAction} to add and start
});
}
+ void setSystemAudioMode(boolean on) {
+ synchronized (mLock) {
+ mSystemAudioMode = on;
+ }
+ }
+
+ boolean getSystemAudioMode() {
+ synchronized (mLock) {
+ return mSystemAudioMode;
+ }
+ }
+
// See if we have an action of a given type in progress.
private <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
for (FeatureAction action : mActions) {
* @return {@code true} if ARC was in "Enabled" status
*/
boolean setArcStatus(boolean enabled) {
- boolean oldStatus = mArcStatusEnabled;
- // 1. Enable/disable ARC circuit.
- // TODO: call set_audio_return_channel of hal interface.
+ synchronized (mLock) {
+ boolean oldStatus = mArcStatusEnabled;
+ // 1. Enable/disable ARC circuit.
+ // TODO: call set_audio_return_channel of hal interface.
- // 2. Update arc status;
- mArcStatusEnabled = enabled;
- return oldStatus;
+ // 2. Update arc status;
+ mArcStatusEnabled = enabled;
+ return oldStatus;
+ }
}
/**
* devices.
*
* @param callback an interface used to get a list of all remote devices' address
+ * @param pickStrategy strategy how to pick polling candidates
* @param retryCount the number of retry used to send polling message to remote devices
+ * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
*/
- void pollDevices(DevicePollingCallback callback, int retryCount) {
- mCecController.pollDevices(callback, retryCount);
+ void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
+ mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount);
+ }
+
+ private int checkPollStrategy(int pickStrategy) {
+ int strategy = pickStrategy & POLL_STRATEGY_MASK;
+ if (strategy == 0) {
+ throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
+ }
+ int iterationStrategy = pickStrategy & POLL_ITERATION_STRATEGY_MASK;
+ if (iterationStrategy == 0) {
+ throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
+ }
+ return strategy | iterationStrategy;
}
*
* @param sourceAddress a logical address of tv
*/
- void launchDeviceDiscovery(int sourceAddress) {
+ void launchDeviceDiscovery(final int sourceAddress) {
// At first, clear all existing device infos.
mCecController.clearDeviceInfoList();
mCecController.addDeviceInfo(device.getDeviceInfo());
}
- // TODO: start hot-plug detection sequence here.
- // addAndStartAction(new HotplugDetectionAction());
+ addAndStartAction(new HotplugDetectionAction(HdmiControlService.this,
+ sourceAddress));
}
});
addAndStartAction(action);
return;
}
- // Ignore if [Device Discovery Action] is on going ignore message.
+ // Ignore if [Device Discovery Action] is going on.
if (hasAction(DeviceDiscoveryAction.class)) {
Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> "
+ "because Device Discovery Action is on-going:" + message);
mCecController.addDeviceInfo(info);
}
-
private void enforceAccessPermission() {
getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
}
return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM);
}
- void setSystemAudioMode(boolean newMode) {
- assertRunOnServiceThread();
- if (newMode != mSystemAudioMode) {
- // TODO: Need to set the preference for SystemAudioMode.
- // TODO: Need to handle the notification of changing the mode and
- // to identify the notification should be handled in the service or TvSettings.
- mSystemAudioMode = newMode;
- }
- }
-
- boolean getSystemAudioMode() {
- assertRunOnServiceThread();
- return mSystemAudioMode;
- }
-
void setAudioStatus(boolean mute, int volume) {
// TODO: Hook up with AudioManager.
}
--- /dev/null
+/*
+ * 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.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+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.
+ */
+final class HotplugDetectionAction extends FeatureAction {
+ 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 POLL_RETRY_COUNT = 2;
+
+ // 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 = HdmiCec.ADDR_SPECIFIC_USE - HdmiCec.ADDR_TV + 1;
+
+ private int mTimeoutCount = 0;
+
+ /**
+ * Constructor
+ *
+ * @param service instance of {@link HdmiControlService}
+ * @param sourceAddress logical address of a device that initiate this action
+ */
+ HotplugDetectionAction(HdmiControlService service, int sourceAddress) {
+ super(service, sourceAddress);
+ }
+
+ @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();
+ }
+ }
+
+ // This method is called every 5 seconds.
+ private void pollDevices() {
+ // All device check called every 15 seconds.
+ if (mTimeoutCount == 0) {
+ pollAllDevices();
+ } else {
+ if (mService.getSystemAudioMode()) {
+ pollAudioSystem();
+ }
+ }
+
+ addTimer(mState, POLLING_INTERVAL_MS);
+ }
+
+ private void pollAllDevices() {
+ Slog.v(TAG, "Poll all devices.");
+
+ mService.pollDevices(new DevicePollingCallback() {
+ @Override
+ public void onPollingFinished(List<Integer> ackedAddress) {
+ checkHotplug(ackedAddress, false);
+ }
+ }, HdmiControlService.POLL_ITERATION_IN_ORDER
+ | HdmiControlService.POLL_STRATEGY_REMOTES_DEVICES, POLL_RETRY_COUNT);
+ }
+
+ private void pollAudioSystem() {
+ Slog.v(TAG, "Poll audio system.");
+
+ mService.pollDevices(new DevicePollingCallback() {
+ @Override
+ public void onPollingFinished(List<Integer> ackedAddress) {
+ checkHotplug(ackedAddress, false);
+ }
+ }, HdmiControlService.POLL_ITERATION_IN_ORDER
+ | HdmiControlService.POLL_STRATEGY_SYSTEM_AUDIO, POLL_RETRY_COUNT);
+ }
+
+ private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
+ BitSet currentInfos = infoListToBitSet(mService.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) {
+ Slog.v(TAG, "Remove device by hot-plug detection:" + index);
+ removeDevice(index);
+ }
+
+ // 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<HdmiCecDeviceInfo> infoList, boolean audioOnly) {
+ BitSet set = new BitSet(NUM_OF_ADDRESS);
+ for (HdmiCecDeviceInfo info : infoList) {
+ if (audioOnly) {
+ if (info.getDeviceType() == HdmiCec.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) {
+ // TODO: implement this.
+ }
+
+ private void removeDevice(int removedAddress) {
+ // TODO: implements following steps.
+ // 1. Launch routing control sequence
+ // 2. Stop one touch play sequence if removed device is the device to be selected.
+ // 3. If audio system, start system audio off and arc off
+ // 4. Inform device remove to others
+ }
+}