OSDN Git Service

Implement cec message cache.
authorJungshik Jang <jayjang@google.com>
Thu, 5 Jun 2014 06:37:59 +0000 (15:37 +0900)
committerJungshik Jang <jayjang@google.com>
Tue, 10 Jun 2014 04:39:19 +0000 (13:39 +0900)
HDMI bus for CEC is quite slow and it may cause conflict
or timeout during device discovery stage or hot-plug
detection stage. However, some static information, such
as physical address, vendor id or osd name are usually
sent automatically when a device is connected, and
almost never change after updated.

Change-Id: I4df9d27ef1af04ee35d63a764580fed07c32d15d

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

index e8862c9..6f42c8b 100644 (file)
@@ -95,10 +95,10 @@ final class DeviceDiscoveryAction extends FeatureAction {
     private int mProcessedDeviceCount = 0;
 
     /**
-     * @Constructor
+     * Constructor.
      *
-     * @param service
-     * @param sourceAddress
+     * @param service an instance of {@link HdmiControlService}.
+     * @param sourceAddress a logical address which initiates this action
      */
     DeviceDiscoveryAction(HdmiControlService service, int sourceAddress,
             DeviceDiscoveryCallback callback) {
@@ -154,6 +154,11 @@ final class DeviceDiscoveryAction extends FeatureAction {
         }
 
         mActionTimer.clearTimerMessage();
+
+        // Check cache first and send request if not exist.
+        if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
+            return;
+        }
         sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, address));
         addTimer(mState, TIMEOUT_MS);
     }
@@ -173,6 +178,10 @@ final class DeviceDiscoveryAction extends FeatureAction {
         }
 
         mActionTimer.clearTimerMessage();
+
+        if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_SET_OSD_NAME)) {
+            return;
+        }
         sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress, address));
         addTimer(mState, TIMEOUT_MS);
     }
@@ -193,10 +202,23 @@ final class DeviceDiscoveryAction extends FeatureAction {
         }
 
         mActionTimer.clearTimerMessage();
+
+        if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_DEVICE_VENDOR_ID)) {
+            return;
+        }
         sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress, address));
         addTimer(mState, TIMEOUT_MS);
     }
 
+    private boolean mayProcessMessageIfCached(int address, int opcode) {
+        HdmiCecMessage message = mService.getCecMessageCache().getMessage(address, opcode);
+        if (message != null) {
+            processCommand(message);
+            return true;
+        }
+        return false;
+    }
+
     @Override
     boolean processCommand(HdmiCecMessage cmd) {
         switch (mState) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageCache.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageCache.java
new file mode 100644 (file)
index 0000000..abda656
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.HdmiCecMessage;
+import android.util.FastImmutableArraySet;
+import android.util.SparseArray;
+
+/**
+ * Cache for incoming message. It caches {@link HdmiCecMessage} with source address and opcode
+ * as a key.
+ *
+ * <p>Note that whenever a device is removed it should call {@link #flushMessagesFrom(int)}
+ * to clean up messages come from the device.
+ */
+final class HdmiCecMessageCache {
+    private static final FastImmutableArraySet<Integer> CACHEABLE_OPCODES = new FastImmutableArraySet<>(
+            new Integer[] {
+                    HdmiCec.MESSAGE_SET_OSD_NAME,
+                    HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+                    HdmiCec.MESSAGE_DEVICE_VENDOR_ID,
+                    HdmiCec.MESSAGE_CEC_VERSION,
+            });
+
+    // It's like [Source Logical Address, [Opcode, HdmiCecMessage]].
+    private final SparseArray<SparseArray<HdmiCecMessage>> mCache = new SparseArray<>();
+
+    HdmiCecMessageCache() {
+    }
+
+    /**
+     * Return a {@link HdmiCecMessage} corresponding to the given {@code address} and
+     * {@code opcode}.
+     *
+     * @param address a logical address of source device
+     * @param opcode opcode of message
+     * @return null if has no {@link HdmiCecMessage} matched to the given {@code address} and {code
+     *         opcode}
+     */
+    public HdmiCecMessage getMessage(int address, int opcode) {
+        SparseArray<HdmiCecMessage> messages = mCache.get(address);
+        if (messages == null) {
+            return null;
+        }
+
+        return messages.get(opcode);
+    }
+
+    /**
+     * Flush all {@link HdmiCecMessage}s sent from the given {@code address}.
+     *
+     * @param address a logical address of source device
+     */
+    public void flushMessagesFrom(int address) {
+        mCache.remove(address);
+    }
+
+    /**
+     * Flush all cached {@link HdmiCecMessage}s.
+     */
+    public void flushAll() {
+        mCache.clear();
+    }
+
+    /**
+     * Cache incoming {@link HdmiCecMessage}. If opcode of message is not listed on
+     * cacheable opcodes list, just ignore it.
+     *
+     * @param message a {@link HdmiCecMessage} to be cached
+     */
+    public void cacheMessage(HdmiCecMessage message) {
+        int opcode = message.getOpcode();
+        if (!isCacheable(opcode)) {
+            return;
+        }
+
+        int source = message.getSource();
+        SparseArray<HdmiCecMessage> messages = mCache.get(source);
+        if (messages == null) {
+            messages = new SparseArray<>();
+            mCache.put(source, messages);
+        }
+        messages.put(opcode, message);
+    }
+
+    private boolean isCacheable(int opcode) {
+        return CACHEABLE_OPCODES.contains(opcode);
+    }
+}
index 4b78591..42cd654 100644 (file)
@@ -117,6 +117,8 @@ public final class HdmiControlService extends SystemService {
     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
             new ArrayList<>();
 
+    private final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
+
     @Nullable
     private HdmiCecController mCecController;
 
@@ -368,6 +370,9 @@ public final class HdmiControlService extends SystemService {
     }
 
     boolean handleCecCommand(HdmiCecMessage message) {
+        // Cache incoming message. Note that it caches only white-listed one.
+        mCecMessageCache.cacheMessage(message);
+
         // Commands that queries system information replies directly instead
         // of creating FeatureAction because they are state-less.
         switch (message.getOpcode()) {
@@ -441,7 +446,6 @@ public final class HdmiControlService extends SystemService {
         return strategy | iterationStrategy;
     }
 
-
     /**
      * Launch device discovery sequence. It starts with clearing the existing device info list.
      * Note that it assumes that logical address of all local devices is already allocated.
@@ -451,6 +455,7 @@ public final class HdmiControlService extends SystemService {
     void launchDeviceDiscovery(final int sourceAddress) {
         // At first, clear all existing device infos.
         mCecController.clearDeviceInfoList();
+        mCecMessageCache.flushAll();
 
         // TODO: check whether TV is one of local devices.
         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress,
@@ -785,4 +790,18 @@ public final class HdmiControlService extends SystemService {
         // TODO: Implement this.
         return false;
     }
+
+    /**
+     * Called when a device is removed or removal of device is detected.
+     *
+     * @param address a logical address of a device to be removed
+     */
+    void removeCecDevice(int address) {
+        mCecController.removeDeviceInfo(address);
+        mCecMessageCache.flushMessagesFrom(address);
+    }
+
+    HdmiCecMessageCache getCecMessageCache() {
+        return mCecMessageCache;
+    }
 }
index c905c76..3e518ea 100644 (file)
@@ -184,6 +184,7 @@ final class HotplugDetectionAction extends FeatureAction {
     }
 
     private void removeDevice(int removedAddress) {
+        mService.removeCecDevice(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.