diff options
4 files changed, 325 insertions, 39 deletions
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index 579f89fadb75..e8862c90eb44 100644 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -124,7 +124,7 @@ final class DeviceDiscoveryAction extends FeatureAction { allocateDevices(ackedAddress); startPhysicalAddressStage(); } - }, DEVICE_POLLING_RETRY); + }, HdmiControlService.POLL_STRATEGY_REMOTES_DEVICES, DEVICE_POLLING_RETRY); return true; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index f329c6907eca..5c420d76e3f5 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -25,6 +25,7 @@ import android.os.MessageQueue; 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; @@ -78,6 +79,22 @@ final class HdmiCecController { 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; @@ -258,10 +275,23 @@ final class HdmiCecController { * 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; + } } /** @@ -363,18 +393,14 @@ final class HdmiCecController { * <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); } @@ -388,6 +414,41 @@ final class HdmiCecController { 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) { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 17e0f384b2f8..4b78591cdae3 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -58,6 +58,14 @@ public final class HdmiControlService extends SystemService { 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. */ @@ -115,10 +123,12 @@ public final class HdmiControlService extends SystemService { @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; @@ -227,6 +237,16 @@ public final class HdmiControlService extends SystemService { } /** + * 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 @@ -242,6 +262,18 @@ public final class HdmiControlService extends SystemService { }); } + 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) { @@ -301,13 +333,15 @@ public final class HdmiControlService extends SystemService { * @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; + } } /** @@ -387,10 +421,24 @@ public final class HdmiControlService extends SystemService { * 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; } @@ -400,7 +448,7 @@ public final class HdmiControlService extends SystemService { * * @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(); @@ -418,8 +466,8 @@ public final class HdmiControlService extends SystemService { mCecController.addDeviceInfo(device.getDeviceInfo()); } - // TODO: start hot-plug detection sequence here. - // addAndStartAction(new HotplugDetectionAction()); + addAndStartAction(new HotplugDetectionAction(HdmiControlService.this, + sourceAddress)); } }); addAndStartAction(action); @@ -438,7 +486,7 @@ public final class HdmiControlService extends SystemService { 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); @@ -585,7 +633,6 @@ public final class HdmiControlService extends SystemService { mCecController.addDeviceInfo(info); } - private void enforceAccessPermission() { getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); } @@ -730,21 +777,6 @@ public final class HdmiControlService extends SystemService { 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. } diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java new file mode 100644 index 000000000000..c905c76a7737 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -0,0 +1,193 @@ +/* + * 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 + } +} |