summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java81
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java88
-rw-r--r--services/core/java/com/android/server/hdmi/HotplugDetectionAction.java193
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
+ }
+}