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

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

Introduce @ServiceThreadOnly and @IothreadOnly to HdmiControlService

Many of hdmi service modules assumes that it runs on
service thread without any lock. But it's a bit
hard to distinguish whether a method runs on
service thread or not even though there is run-time
assertion (runOnServiceThread()) is top of method.
@ServiceThreadOnly interface documents that a method should
run on service thread while @IoThreadOnly documents
that a method should run on io thread.

Change-Id: Ia4a6a48675d2302fb2cb7a8a807fcf112da5d4e7
parent d4c11543
Loading
Loading
Loading
Loading
+47 −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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Annotation container for Hdmi control service package.
 */
public class HdmiAnnotations {
    /**
     * Annotation type to used to mark a method which should be run on service thread.
     * This annotation should go with {@code assertRunOnServiceThread} used to verify
     * whether it's called from service thread.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface ServiceThreadOnly {
    }

    /**
     * Annotation type to used to mark a method which should be run on io thread.
     * This annotation should go with {@code assertRunOnIoThread} used to verify
     * whether it's called from io thread.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface IoThreadOnly {
    }
}
+27 −1
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.util.Predicate;
import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;

import libcore.util.EmptyArray;
@@ -144,7 +146,9 @@ final class HdmiCecController {
        mNativePtr = nativePtr;
    }

    @ServiceThreadOnly
    void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
        assertRunOnServiceThread();
        mLocalDevices.put(deviceType, device);
    }

@@ -161,6 +165,7 @@ final class HdmiCecController {
     *                         Otherwise, scan address will start from {@code preferredAddress}
     * @param callback callback interface to report allocated logical address to caller
     */
    @ServiceThreadOnly
    void allocateLogicalAddress(final int deviceType, final int preferredAddress,
            final AllocateAddressCallback callback) {
        assertRunOnServiceThread();
@@ -173,6 +178,7 @@ final class HdmiCecController {
        });
    }

    @IoThreadOnly
    private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
            final AllocateAddressCallback callback) {
        assertRunOnIoThread();
@@ -245,6 +251,7 @@ final class HdmiCecController {
     * @param newLogicalAddress a logical address to be added
     * @return 0 on success. Otherwise, returns negative value
     */
    @ServiceThreadOnly
    int addLogicalAddress(int newLogicalAddress) {
        assertRunOnServiceThread();
        if (HdmiCec.isValidAddress(newLogicalAddress)) {
@@ -259,6 +266,7 @@ final class HdmiCecController {
     *
     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
     */
    @ServiceThreadOnly
    void clearLogicalAddress() {
        assertRunOnServiceThread();
        // TODO: consider to backup logical address so that new logical address
@@ -277,6 +285,7 @@ final class HdmiCecController {
     * @return CEC physical address of the device. The range of success address
     *         is between 0x0000 and 0xFFFF. If failed it returns -1
     */
    @ServiceThreadOnly
    int getPhysicalAddress() {
        assertRunOnServiceThread();
        return nativeGetPhysicalAddress(mNativePtr);
@@ -287,6 +296,7 @@ final class HdmiCecController {
     *
     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
     */
    @ServiceThreadOnly
    int getVersion() {
        assertRunOnServiceThread();
        return nativeGetVersion(mNativePtr);
@@ -297,6 +307,7 @@ final class HdmiCecController {
     *
     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
     */
    @ServiceThreadOnly
    int getVendorId() {
        assertRunOnServiceThread();
        return nativeGetVendorId(mNativePtr);
@@ -311,6 +322,7 @@ final class HdmiCecController {
     * @param value a value of option. Actual value varies flag. For more
     *        details, look at description of flags
     */
    @ServiceThreadOnly
    void setOption(int flag, int value) {
        assertRunOnServiceThread();
        nativeSetOption(mNativePtr, flag, value);
@@ -321,6 +333,7 @@ final class HdmiCecController {
     *
     * @param enabled whether to enable/disable ARC
     */
    @ServiceThreadOnly
    void setAudioReturnChannel(boolean enabled) {
        assertRunOnServiceThread();
        nativeSetAudioReturnChannel(mNativePtr, enabled);
@@ -332,6 +345,7 @@ final class HdmiCecController {
     * @param port port number to check connection status
     * @return true if connected; otherwise, return false
     */
    @ServiceThreadOnly
    boolean isConnected(int port) {
        assertRunOnServiceThread();
        return nativeIsConnected(mNativePtr, port);
@@ -347,6 +361,7 @@ final class HdmiCecController {
     * @param pickStrategy strategy how to pick polling candidates
     * @param retryCount the number of retry used to send polling message to remote devices
     */
    @ServiceThreadOnly
    void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
        assertRunOnServiceThread();

@@ -360,6 +375,7 @@ final class HdmiCecController {
     *
     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
     */
    @ServiceThreadOnly
    List<HdmiCecLocalDevice> getLocalDeviceList() {
        assertRunOnServiceThread();
        return HdmiUtils.sparseArrayToList(mLocalDevices);
@@ -400,7 +416,9 @@ final class HdmiCecController {
        return pollingCandidates;
    }

    @ServiceThreadOnly
    private boolean isAllocatedLocalDeviceAddress(int address) {
        assertRunOnServiceThread();
        for (int i = 0; i < mLocalDevices.size(); ++i) {
            if (mLocalDevices.valueAt(i).isAddressOf(address)) {
                return true;
@@ -409,6 +427,7 @@ final class HdmiCecController {
        return false;
    }

    @ServiceThreadOnly
    private void runDevicePolling(final List<Integer> candidates, final int retryCount,
            final DevicePollingCallback callback) {
        assertRunOnServiceThread();
@@ -433,6 +452,7 @@ final class HdmiCecController {
        });
    }

    @IoThreadOnly
    private boolean sendPollMessage(int address, int retryCount) {
        assertRunOnIoThread();
        for (int i = 0; i < retryCount; ++i) {
@@ -480,6 +500,7 @@ final class HdmiCecController {
        return isAllocatedLocalDeviceAddress(address);
    }

    @ServiceThreadOnly
    private void onReceiveCommand(HdmiCecMessage message) {
        assertRunOnServiceThread();
        if (isAcceptableAddress(message.getDestination())
@@ -493,16 +514,20 @@ final class HdmiCecController {
            HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
                    sourceAddress, message.getSource(), message.getOpcode(),
                    HdmiConstants.ABORT_REFUSED);
            sendCommand(cecMessage, null);
            sendCommand(cecMessage);
        }
    }

    @ServiceThreadOnly
    void sendCommand(HdmiCecMessage cecMessage) {
        assertRunOnServiceThread();
        sendCommand(cecMessage, null);
    }

    @ServiceThreadOnly
    void sendCommand(final HdmiCecMessage cecMessage,
            final HdmiControlService.SendMessageCallback callback) {
        assertRunOnServiceThread();
        runOnIoThread(new Runnable() {
            @Override
            public void run() {
@@ -527,6 +552,7 @@ final class HdmiCecController {
    /**
     * Called by native when incoming CEC message arrived.
     */
    @ServiceThreadOnly
    private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
        assertRunOnServiceThread();
        onReceiveCommand(HdmiCecMessageBuilder.of(srcAddress, dstAddress, body));
+30 −7
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.os.Looper;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;

import java.util.ArrayList;
import java.util.Iterator;
@@ -87,7 +88,9 @@ abstract class HdmiCecLocalDevice {
        }
    }

    @ServiceThreadOnly
    void init() {
        assertRunOnServiceThread();
        mPreferredAddress = HdmiCec.ADDR_UNREGISTERED;
        // TODO: load preferred address from permanent storage.
    }
@@ -103,9 +106,9 @@ abstract class HdmiCecLocalDevice {
     * @param message incoming message
     * @return true if consumed a message; otherwise, return false.
     */
    @ServiceThreadOnly
    final boolean dispatchMessage(HdmiCecMessage message) {
        assertRunOnServiceThread();

        int dest = message.getDestination();
        if (dest != mAddress && dest != HdmiCec.ADDR_BROADCAST) {
            return false;
@@ -115,9 +118,9 @@ abstract class HdmiCecLocalDevice {
        return onMessage(message);
    }

    @ServiceThreadOnly
    protected final boolean onMessage(HdmiCecMessage message) {
        assertRunOnServiceThread();

        if (dispatchMessageToAction(message)) {
            return true;
        }
@@ -147,7 +150,9 @@ abstract class HdmiCecLocalDevice {
        }
    }

    @ServiceThreadOnly
    private boolean dispatchMessageToAction(HdmiCecMessage message) {
        assertRunOnServiceThread();
        for (FeatureAction action : mActions) {
            if (action.processCommand(message)) {
                return true;
@@ -156,6 +161,7 @@ abstract class HdmiCecLocalDevice {
        return false;
    }

    @ServiceThreadOnly
    protected boolean handleGivePhysicalAddress() {
        assertRunOnServiceThread();

@@ -166,9 +172,9 @@ abstract class HdmiCecLocalDevice {
        return true;
    }

    @ServiceThreadOnly
    protected boolean handleGiveDeviceVendorId() {
        assertRunOnServiceThread();

        int vendorId = mService.getVendorId();
        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
                mAddress, vendorId);
@@ -176,9 +182,9 @@ abstract class HdmiCecLocalDevice {
        return true;
    }

    @ServiceThreadOnly
    protected boolean handleGetCecVersion(HdmiCecMessage message) {
        assertRunOnServiceThread();

        int version = mService.getCecVersion();
        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
                message.getSource(), version);
@@ -186,9 +192,9 @@ abstract class HdmiCecLocalDevice {
        return true;
    }

    @ServiceThreadOnly
    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
        assertRunOnServiceThread();

        Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
        mService.sendCecCommand(
                HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
@@ -197,9 +203,9 @@ abstract class HdmiCecLocalDevice {
        return true;
    }

    @ServiceThreadOnly
    protected boolean handleGiveOsdName(HdmiCecMessage message) {
        assertRunOnServiceThread();

        // Note that since this method is called after logical address allocation is done,
        // mDeviceInfo should not be null.
        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
@@ -236,45 +242,52 @@ abstract class HdmiCecLocalDevice {
        return false;
    }

    @ServiceThreadOnly
    final void handleAddressAllocated(int logicalAddress) {
        assertRunOnServiceThread();

        mAddress = mPreferredAddress = logicalAddress;
        onAddressAllocated(logicalAddress);
    }

    @ServiceThreadOnly
    HdmiCecDeviceInfo getDeviceInfo() {
        assertRunOnServiceThread();
        return mDeviceInfo;
    }

    @ServiceThreadOnly
    void setDeviceInfo(HdmiCecDeviceInfo info) {
        assertRunOnServiceThread();
        mDeviceInfo = info;
    }

    // Returns true if the logical address is same as the argument.
    @ServiceThreadOnly
    boolean isAddressOf(int addr) {
        assertRunOnServiceThread();
        return addr == mAddress;
    }

    // Resets the logical address to unregistered(15), meaning the logical device is invalid.
    @ServiceThreadOnly
    void clearAddress() {
        assertRunOnServiceThread();
        mAddress = HdmiCec.ADDR_UNREGISTERED;
    }

    @ServiceThreadOnly
    void setPreferredAddress(int addr) {
        assertRunOnServiceThread();
        mPreferredAddress = addr;
    }

    @ServiceThreadOnly
    int getPreferredAddress() {
        assertRunOnServiceThread();
        return mPreferredAddress;
    }

    @ServiceThreadOnly
    void addAndStartAction(final FeatureAction action) {
        assertRunOnServiceThread();
        mActions.add(action);
@@ -282,6 +295,7 @@ abstract class HdmiCecLocalDevice {
    }

    // See if we have an action of a given type in progress.
    @ServiceThreadOnly
    <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
        assertRunOnServiceThread();
        for (FeatureAction action : mActions) {
@@ -293,6 +307,7 @@ abstract class HdmiCecLocalDevice {
    }

    // Returns all actions matched with given class type.
    @ServiceThreadOnly
    <T extends FeatureAction> List<T> getActions(final Class<T> clazz) {
        assertRunOnServiceThread();
        ArrayList<T> actions = new ArrayList<>();
@@ -309,17 +324,21 @@ abstract class HdmiCecLocalDevice {
     *
     * @param action {@link FeatureAction} to remove
     */
    @ServiceThreadOnly
    void removeAction(final FeatureAction action) {
        assertRunOnServiceThread();
        mActions.remove(action);
    }

    // Remove all actions matched with the given Class type.
    @ServiceThreadOnly
    <T extends FeatureAction> void removeAction(final Class<T> clazz) {
        assertRunOnServiceThread();
        removeActionExcept(clazz, null);
    }

    // Remove all actions matched with the given Class type besides |exception|.
    @ServiceThreadOnly
    <T extends FeatureAction> void removeActionExcept(final Class<T> clazz,
            final FeatureAction exception) {
        assertRunOnServiceThread();
@@ -352,7 +371,9 @@ abstract class HdmiCecLocalDevice {
        return mService;
    }

    @ServiceThreadOnly
    final boolean isConnectedToArcPort(int path) {
        assertRunOnServiceThread();
        return mService.isConnectedToArcPort(path);
    }

@@ -442,11 +463,13 @@ abstract class HdmiCecLocalDevice {
        }
    }

    @ServiceThreadOnly
    HdmiCecMessageCache getCecMessageCache() {
        assertRunOnServiceThread();
        return mCecMessageCache;
    }

    @ServiceThreadOnly
    int pathToPortId(int newPath) {
        assertRunOnServiceThread();
        return mService.pathToPortId(newPath);
+10 −0
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;

import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;

/**
 * Represent a logical device of type Playback residing in Android system.
 */
@@ -32,11 +34,14 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
    }

    @Override
    @ServiceThreadOnly
    protected void onAddressAllocated(int logicalAddress) {
        assertRunOnServiceThread();
        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
                mAddress, mService.getPhysicalAddress(), mDeviceType));
    }

    @ServiceThreadOnly
    void oneTouchPlay(IHdmiControlCallback callback) {
        assertRunOnServiceThread();
        if (hasAction(OneTouchPlayAction.class)) {
@@ -56,6 +61,7 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
        addAndStartAction(action);
    }

    @ServiceThreadOnly
    void queryDisplayStatus(IHdmiControlCallback callback) {
        assertRunOnServiceThread();
        if (hasAction(DevicePowerStatusAction.class)) {
@@ -73,7 +79,9 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
        addAndStartAction(action);
    }

    @ServiceThreadOnly
    private void invokeCallback(IHdmiControlCallback callback, int result) {
        assertRunOnServiceThread();
        try {
            callback.onComplete(result);
        } catch (RemoteException e) {
@@ -82,7 +90,9 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
    }

    @Override
    @ServiceThreadOnly
    void onHotplug(int portId, boolean connected) {
        assertRunOnServiceThread();
        // TODO: clear devices connected to the given port id.
        mCecMessageCache.flushAll();
    }
+29 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.util.SparseArray;

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

import java.util.ArrayList;
import java.util.Collections;
@@ -66,6 +67,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    }

    @Override
    @ServiceThreadOnly
    protected void onAddressAllocated(int logicalAddress) {
        assertRunOnServiceThread();
        // TODO: vendor-specific initialization here.
@@ -76,7 +78,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
                mAddress, mService.getVendorId()));

        launchDeviceDiscovery();
        // TODO: Start routing control action, device discovery action.
        // TODO: Start routing control action
    }

    /**
@@ -85,6 +87,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     * @param targetAddress logical address of the device to select
     * @param callback callback object to report the result with
     */
    @ServiceThreadOnly
    void deviceSelect(int targetAddress, IHdmiControlCallback callback) {
        assertRunOnServiceThread();
        HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
@@ -102,6 +105,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     * @param portId new HDMI port to route to
     * @param callback callback object to report the result with
     */
    @ServiceThreadOnly
    void portSelect(int portId, IHdmiControlCallback callback) {
        assertRunOnServiceThread();
        if (isInPresetInstallationMode()) {
@@ -136,6 +140,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
     * @param isPressed true if this is keypress event
     */
    @ServiceThreadOnly
    void sendKeyEvent(int keyCode, boolean isPressed) {
        assertRunOnServiceThread();
        List<SendKeyAction> action = getActions(SendKeyAction.class);
@@ -162,6 +167,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    }

    @Override
    @ServiceThreadOnly
    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
        assertRunOnServiceThread();
        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
@@ -176,6 +182,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    }

    @Override
    @ServiceThreadOnly
    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
        assertRunOnServiceThread();
        // Ignore if [Device Discovery Action] is going on.
@@ -198,6 +205,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    }

    @Override
    @ServiceThreadOnly
    protected boolean handleVendorSpecificCommand(HdmiCecMessage message) {
        assertRunOnServiceThread();
        List<VendorSpecificAction> actions = Collections.emptyList();
@@ -223,6 +231,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
        return true;
    }

    @ServiceThreadOnly
    private void launchDeviceDiscovery() {
        assertRunOnServiceThread();
        clearDeviceInfoList();
@@ -257,12 +266,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    }

    // Clear all device info.
    @ServiceThreadOnly
    private void clearDeviceInfoList() {
        assertRunOnServiceThread();
        mDeviceInfos.clear();
        updateSafeDeviceInfoList();
    }

    @ServiceThreadOnly
    void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
        assertRunOnServiceThread();
        HdmiCecDeviceInfo avr = getAvrDeviceInfo();
@@ -324,11 +335,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
        }
    }

    @ServiceThreadOnly
    void setAudioStatus(boolean mute, int volume) {
        mService.setAudioStatus(mute, volume);
    }

    @Override
    @ServiceThreadOnly
    protected boolean handleInitiateArc(HdmiCecMessage message) {
        assertRunOnServiceThread();
        // In case where <Initiate Arc> is started by <Request ARC Initiation>
@@ -341,6 +354,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    }

    @Override
    @ServiceThreadOnly
    protected boolean handleTerminateArc(HdmiCecMessage message) {
        assertRunOnServiceThread();
        // In case where <Terminate Arc> is started by <Request ARC Termination>
@@ -355,6 +369,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    }

    @Override
    @ServiceThreadOnly
    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
        assertRunOnServiceThread();
        if (!isMessageForSystemAudio(message)) {
@@ -367,6 +382,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    }

    @Override
    @ServiceThreadOnly
    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
        assertRunOnServiceThread();
        if (!isMessageForSystemAudio(message)) {
@@ -396,6 +412,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo}
     *         that has the same logical address as new one has.
     */
    @ServiceThreadOnly
    HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
        assertRunOnServiceThread();
        HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
@@ -416,6 +433,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     * @param logicalAddress logical address of device to be removed
     * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
     */
    @ServiceThreadOnly
    HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
        assertRunOnServiceThread();
        HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
@@ -432,6 +450,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
     * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfoList(boolean)}.
     */
    @ServiceThreadOnly
    List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includelLocalDevice) {
        assertRunOnServiceThread();
        if (includelLocalDevice) {
@@ -463,6 +482,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
        }
    }

    @ServiceThreadOnly
    private void updateSafeDeviceInfoList() {
        assertRunOnServiceThread();
        List<HdmiCecDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
@@ -473,6 +493,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
        }
    }

    @ServiceThreadOnly
    private boolean isLocalDeviceAddress(int address) {
        assertRunOnServiceThread();
        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
@@ -493,11 +514,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
     *         Returns null if no logical address matched
     */
    @ServiceThreadOnly
    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
        assertRunOnServiceThread();
        return mDeviceInfos.get(logicalAddress);
    }

    @ServiceThreadOnly
    HdmiCecDeviceInfo getAvrDeviceInfo() {
        assertRunOnServiceThread();
        return getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM);
@@ -525,6 +548,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     *
     * @param info device info of a new device.
     */
    @ServiceThreadOnly
    final void addCecDevice(HdmiCecDeviceInfo info) {
        assertRunOnServiceThread();
        addDeviceInfo(info);
@@ -540,6 +564,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     *
     * @param address a logical address of a device to be removed
     */
    @ServiceThreadOnly
    final void removeCecDevice(int address) {
        assertRunOnServiceThread();
        HdmiCecDeviceInfo info = removeDeviceInfo(address);
@@ -555,6 +580,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     * @param path routing path or physical address
     * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null
     */
    @ServiceThreadOnly
    final HdmiCecDeviceInfo getDeviceInfoByPath(int path) {
        assertRunOnServiceThread();
        for (HdmiCecDeviceInfo info : getDeviceInfoList(false)) {
@@ -574,6 +600,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     * @param logicalAddress logical address of a device to be searched
     * @return true if exist; otherwise false
     */
    @ServiceThreadOnly
    boolean isInDeviceList(int physicalAddress, int logicalAddress) {
        assertRunOnServiceThread();
        HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress);
@@ -584,6 +611,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    }

    @Override
    @ServiceThreadOnly
    void onHotplug(int portNo, boolean connected) {
        assertRunOnServiceThread();
        // TODO: delegate onHotplug event to each local device.
Loading