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

Commit ae8ef37c authored by Jungshik Jang's avatar Jungshik Jang Committed by Android (Google) Code Review
Browse files

Merge "DO NOT MERGE: Implement device discovery sequence." into lmp-preview-dev

parents 9bb6fbb7 d42a7a32
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);
    }