Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d42a7a32 authored by Jungshik Jang's avatar Jungshik Jang
Browse files

DO NOT MERGE: Implement device discovery sequence.

When device discover is launched it goes through the following step

1. clear all existing devices
2. send <Polling Message> of all logical addresses
  excecpt one of local device
3. Once got all allocated logical addresses, gather physical address of
  them
4. Once got physical address of them, gather display name of them
5. Once got display names, gather vendor id of them
5. Once got vendor id of them, register all gathered info to
  internal device info list.

Change-Id: Ic9aca3b15d88ac7650f10b6d0bfa9c97923975e8
parent a8fd44b7
Loading
Loading
Loading
Loading
+361 −0
Original line number Diff line number Diff line
/*
 * 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.internal.util.Preconditions;
import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
 * Feature action that handles device discovery sequences.
 * Device discovery is launched when TV device is woken from "Standby" state
 * or enabled "Control for Hdmi" from disabled state.
 *
 * <p>Device discovery goes through the following steps.
 * <ol>
 *   <li>Poll all non-local devices by sending &lt;Polling Message&gt;
 *   <li>Gather "Physical address" and "device type" of all acknowledged devices
 *   <li>Gather "OSD (display) name" of all acknowledge devices
 *   <li>Gather "Vendor id" of all acknowledge devices
 * </ol>
 */
final class DeviceDiscoveryAction extends FeatureAction {
    private static final String TAG = "DeviceDiscoveryAction";

    // State in which the action is waiting for device polling.
    private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1;
    // State in which the action is waiting for gathering physical address of non-local devices.
    private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2;
    // State in which the action is waiting for gathering osd name of non-local devices.
    private static final int STATE_WAITING_FOR_OSD_NAME = 3;
    // State in which the action is waiting for gathering vendor id of non-local devices.
    private static final int STATE_WAITING_FOR_VENDOR_ID = 4;

    private static final int DEVICE_POLLING_RETRY = 1;

    // TODO: Move this to common place
    private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;

    /**
     * Interface used to report result of device discovery.
     */
    interface DeviceDiscoveryCallback {
        /**
         * Called when device discovery is done.
         *
         * @param deviceInfos a list of all non-local devices. It can be empty list.
         */
        void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos);
    }

    // An internal container used to keep track of device information during
    // this action.
    private static final class DeviceInfo {
        private final int mLogicalAddress;

        private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
        private int mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
        private String mDisplayName = "";
        private int mDeviceType = HdmiCec.DEVICE_INACTIVE;

        private DeviceInfo(int logicalAddress) {
            mLogicalAddress = logicalAddress;
        }

        private HdmiCecDeviceInfo toHdmiCecDeviceInfo() {
            return new HdmiCecDeviceInfo(mLogicalAddress, mPhysicalAddress, mDeviceType, mVendorId,
                    mDisplayName);
        }
    }

    private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
    private final DeviceDiscoveryCallback mCallback;
    private int mProcessedDeviceCount = 0;

    /**
     * @Constructor
     *
     * @param service
     * @param sourceAddress
     */
    DeviceDiscoveryAction(HdmiControlService service, int sourceAddress,
            DeviceDiscoveryCallback callback) {
        super(service, sourceAddress);
        mCallback = Preconditions.checkNotNull(callback);
    }

    @Override
    boolean start() {
        mDevices.clear();
        mState = STATE_WAITING_FOR_DEVICE_POLLING;

        mService.pollDevices(new DevicePollingCallback() {
            @Override
            public void onPollingFinished(List<Integer> ackedAddress) {
                if (ackedAddress.isEmpty()) {
                    Slog.i(TAG, "No device is detected.");
                    finish();
                    return;
                }

                Slog.i(TAG, "Device detected: " + ackedAddress);
                allocateDevices(ackedAddress);
                startPhysicalAddressStage();
            }
        }, DEVICE_POLLING_RETRY);
        return true;
    }

    private void allocateDevices(List<Integer> addresses) {
        for (Integer i : addresses) {
            DeviceInfo info = new DeviceInfo(i);
            mDevices.add(info);
        }
    }

    private void startPhysicalAddressStage() {
        mProcessedDeviceCount = 0;
        mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;

        checkAndProceedStage();
    }

    private boolean verifyValidLogicalAddress(int address) {
        return address >= HdmiCec.ADDR_TV && address < HdmiCec.ADDR_UNREGISTERED;
    }

    private void queryPhysicalAddress(int address) {
        if (!verifyValidLogicalAddress(address)) {
            checkAndProceedStage();
            return;
        }

        mActionTimer.clearTimerMessage();
        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, address));
        addTimer(mState, TIMEOUT_MS);
    }

    private void startOsdNameStage() {
        mProcessedDeviceCount = 0;
        mState = STATE_WAITING_FOR_OSD_NAME;

        checkAndProceedStage();
    }

    private void queryOsdName(int address) {
        if (!verifyValidLogicalAddress(address)) {
            checkAndProceedStage();
            return;
        }

        mActionTimer.clearTimerMessage();
        sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress, address));
        addTimer(mState, TIMEOUT_MS);
    }

    private void startVendorIdStage() {
        mProcessedDeviceCount = 0;
        mState = STATE_WAITING_FOR_VENDOR_ID;

        checkAndProceedStage();
    }

    private void queryVendorId(int address) {
        if (!verifyValidLogicalAddress(address)) {
            checkAndProceedStage();
            return;
        }

        mActionTimer.clearTimerMessage();
        sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress, address));
        addTimer(mState, TIMEOUT_MS);
    }

    @Override
    boolean processCommand(HdmiCecMessage cmd) {
        switch (mState) {
            case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
                if (cmd.getOpcode() == HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
                    handleReportPhysicalAddress(cmd);
                    return true;
                }
                return false;
            case STATE_WAITING_FOR_OSD_NAME:
                if (cmd.getOpcode() == HdmiCec.MESSAGE_SET_OSD_NAME) {
                    handleSetOsdName(cmd);
                    return true;
                }
                return false;
            case STATE_WAITING_FOR_VENDOR_ID:
                if (cmd.getOpcode() == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) {
                    handleVendorId(cmd);
                    return true;
                }
                return false;
            case STATE_WAITING_FOR_DEVICE_POLLING:
                // Fall through.
            default:
                return false;
        }
    }

    private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());

        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
        if (current.mLogicalAddress != cmd.getSource()) {
            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
                    cmd.getSource());
            return;
        }

        byte params[] = cmd.getParams();
        if (params.length == 3) {
            current.mPhysicalAddress = ((params[0] & 0xFF) << 8) | (params[1] & 0xFF);
            current.mDeviceType = params[2] & 0xFF;

            increaseProcessedDeviceCount();
            checkAndProceedStage();
        } else {
            // Physical address is a critical element in device info.
            // If failed, remove device from device list and proceed to the next device.
            removeDevice(mProcessedDeviceCount);
            checkAndProceedStage();
        }
    }

    private void handleSetOsdName(HdmiCecMessage cmd) {
        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());

        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
        if (current.mLogicalAddress != cmd.getSource()) {
            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
                    cmd.getSource());
            return;
        }

        String displayName = null;
        try {
            displayName = new String(cmd.getParams(), "US-ASCII");
        } catch (UnsupportedEncodingException e) {
            Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
            // If failed to get display name, use the default name of device.
            displayName = HdmiCec.getDefaultDeviceName(current.mLogicalAddress);
        }
        current.mDisplayName = displayName;
        increaseProcessedDeviceCount();
        checkAndProceedStage();
    }

    private void handleVendorId(HdmiCecMessage cmd) {
        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());

        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
        if (current.mLogicalAddress != cmd.getSource()) {
            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
                    cmd.getSource());
            return;
        }

        byte[] params = cmd.getParams();
        if (params.length == 3) {
            int vendorId = ((params[0] & 0xFF) << 16)
                    | ((params[1] & 0xFF) << 8)
                    | (params[2] & 0xFF);
            current.mVendorId = vendorId;
        } else {
            Slog.w(TAG, "Invalid vendor id: " + cmd.toString());
        }
        increaseProcessedDeviceCount();
        checkAndProceedStage();
    }

    private void increaseProcessedDeviceCount() {
        mProcessedDeviceCount++;
    }

    private void removeDevice(int index) {
        mDevices.remove(index);
    }

    private void wrapUpAndFinish() {
        ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>();
        for (DeviceInfo info : mDevices) {
            HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo();
            result.add(cecDeviceInfo);
        }
        mCallback.onDeviceDiscoveryDone(result);
        finish();
    }

    private void checkAndProceedStage() {
        if (mDevices.isEmpty()) {
            wrapUpAndFinish();
            return;
        }

        // If finished current stage, move on to next stage.
        if (mProcessedDeviceCount == mDevices.size()) {
            mProcessedDeviceCount = 0;
            switch (mState) {
                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
                    startOsdNameStage();
                    return;
                case STATE_WAITING_FOR_OSD_NAME:
                    startVendorIdStage();
                    return;
                case STATE_WAITING_FOR_VENDOR_ID:
                    wrapUpAndFinish();
                    return;
                default:
                    return;
            }
        } else {
            int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
            switch (mState) {
                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
                    queryPhysicalAddress(address);
                    return;
                case STATE_WAITING_FOR_OSD_NAME:
                    queryOsdName(address);
                    return;
                case STATE_WAITING_FOR_VENDOR_ID:
                    queryVendorId(address);
                default:
                    return;
            }
        }
    }

    @Override
    void handleTimerEvent(int state) {
        if (mState == STATE_NONE || mState != state) {
            return;
        }

        removeDevice(mProcessedDeviceCount);
        checkAndProceedStage();
    }
}
+29 −6
Original line number Diff line number Diff line
@@ -260,6 +260,16 @@ final class HdmiCecController {
        return deviceInfo;
    }

    /**
     * Clear all device info.
     *
     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
     */
    void clearDeviceInfoList() {
        assertRunOnServiceThread();
        mDeviceInfos.clear();
    }

    /**
     * Return a list of all {@link HdmiCecDeviceInfo}.
     *
@@ -267,12 +277,7 @@ final class HdmiCecController {
     */
    List<HdmiCecDeviceInfo> getDeviceInfoList() {
        assertRunOnServiceThread();

        List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<>(mDeviceInfos.size());
        for (int i = 0; i < mDeviceInfos.size(); ++i) {
            deviceInfoList.add(mDeviceInfos.valueAt(i));
        }
        return deviceInfoList;
        return sparseArrayToList(mDeviceInfos);
    }

    /**
@@ -389,6 +394,24 @@ final class HdmiCecController {
        runDevicePolling(pollingCandidates, retryCount, callback);
    }

    /**
     * Return a list of all {@link HdmiCecLocalDevice}s.
     *
     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
     */
    List<HdmiCecLocalDevice> getLocalDeviceList() {
        assertRunOnServiceThread();
        return sparseArrayToList(mLocalDevices);
    }

    private static <T> List<T> sparseArrayToList(SparseArray<T> array) {
        ArrayList<T> list = new ArrayList<>();
        for (int i = 0; i < array.size(); ++i) {
            list.add(array.valueAt(i));
        }
        return list;
    }

    private boolean isAllocatedLocalDeviceAddress(int address) {
        for (int i = 0; i < mLocalDevices.size(); ++i) {
            if (mLocalDevices.valueAt(i).isAddressOf(address)) {
+11 −0
Original line number Diff line number Diff line
@@ -72,6 +72,17 @@ public class HdmiCecMessageBuilder {
        return buildCommand(src, dest, HdmiCec.MESSAGE_FEATURE_ABORT, params);
    }

    /**
     * Build &lt;Give Physical Address&gt; command.
     *
     * @param src source address of command
     * @param dest destination address of command
     * @return newly created {@link HdmiCecMessage}
     */
    static HdmiCecMessage buildGivePhysicalAddress(int src, int dest) {
        return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS);
    }

    /**
     * Build &lt;Give Osd Name&gt; command.
     *
+33 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;

import java.util.ArrayList;
import java.util.Iterator;
@@ -411,6 +412,38 @@ public final class HdmiControlService extends SystemService {
        }
    }

    void addCecDevice(HdmiCecDeviceInfo info) {
        mCecController.addDeviceInfo(info);
    }

    // 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.
    void launchDeviceDiscovery() {
        // At first, clear all existing device infos.
        mCecController.clearDeviceInfoList();

        // TODO: check whether TV is one of local devices.
        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, HdmiCec.ADDR_TV,
                new DeviceDiscoveryCallback() {
                    @Override
                    public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
                        for (HdmiCecDeviceInfo info : deviceInfos) {
                            mCecController.addDeviceInfo(info);
                        }

                        // Add device info of all local devices.
                        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
                            mCecController.addDeviceInfo(device.getDeviceInfo());
                        }

                        // TODO: start hot-plug detection sequence here.
                        // addAndStartAction(new HotplugDetectionAction());
                    }
                });
        addAndStartAction(action);
    }

    private void enforceAccessPermission() {
        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
    }