OSDN Git Service

Implement hotplug detection sequence.
authorJungshik Jang <jayjang@google.com>
Wed, 28 May 2014 08:58:58 +0000 (17:58 +0900)
committerJungshik Jang <jayjang@google.com>
Thu, 5 Jun 2014 01:36:35 +0000 (10:36 +0900)
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

services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
services/core/java/com/android/server/hdmi/HdmiCecController.java
services/core/java/com/android/server/hdmi/HdmiControlService.java
services/core/java/com/android/server/hdmi/HotplugDetectionAction.java [new file with mode: 0644]

index 579f89f..e8862c9 100644 (file)
@@ -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;
     }
 
index f329c69..5c420d7 100644 (file)
@@ -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) {
index 17e0f38..4b78591 100644 (file)
@@ -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 (file)
index 0000000..c905c76
--- /dev/null
@@ -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
+    }
+}