am 553aeed9: Merge "CEC: Buffer messages coming from unknown device" into lmp-mr1-dev
automerge: cccafac
* commit 'cccafac36c7c0297afbfb17d4389951b77066cab':
CEC: Buffer messages coming from unknown device
This commit is contained in:
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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.HdmiDeviceInfo;
|
||||||
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer storage to keep incoming messages for later processing. Used to
|
||||||
|
* handle messages that arrive when the device is not ready. Useful when
|
||||||
|
* keeping the messages from a connected device which are not discovered yet.
|
||||||
|
*/
|
||||||
|
final class DelayedMessageBuffer {
|
||||||
|
private final ArrayList<HdmiCecMessage> mBuffer = new ArrayList<>();
|
||||||
|
private final HdmiCecLocalDevice mDevice;
|
||||||
|
|
||||||
|
DelayedMessageBuffer(HdmiCecLocalDevice device) {
|
||||||
|
mDevice = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new message to the buffer. The buffer keeps selected messages in
|
||||||
|
* the order they are received.
|
||||||
|
*
|
||||||
|
* @param message {@link HdmiCecMessage} to add
|
||||||
|
*/
|
||||||
|
void add(HdmiCecMessage message) {
|
||||||
|
boolean buffered = true;
|
||||||
|
|
||||||
|
// Note that all the messages are not handled in the same manner.
|
||||||
|
// For <Active Source> we keep the latest one only.
|
||||||
|
// TODO: This might not be the best way to choose the active source.
|
||||||
|
// Devise a better way to pick up the best one.
|
||||||
|
switch (message.getOpcode()) {
|
||||||
|
case Constants.MESSAGE_ACTIVE_SOURCE:
|
||||||
|
removeActiveSource();
|
||||||
|
mBuffer.add(message);
|
||||||
|
break;
|
||||||
|
case Constants.MESSAGE_INITIATE_ARC:
|
||||||
|
mBuffer.add(message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
buffered = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (buffered) {
|
||||||
|
HdmiLogger.debug("Buffering message:" + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeActiveSource() {
|
||||||
|
// Uses iterator to remove elements while looping through the list.
|
||||||
|
for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) {
|
||||||
|
HdmiCecMessage message = iter.next();
|
||||||
|
if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processAllMessages() {
|
||||||
|
for (HdmiCecMessage message : mBuffer) {
|
||||||
|
mDevice.onMessage(message);
|
||||||
|
HdmiLogger.debug("Processing message:" + message);
|
||||||
|
}
|
||||||
|
mBuffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process messages from a given logical device. Called by
|
||||||
|
* {@link NewDeviceAction} actions when they finish adding the device
|
||||||
|
* information.
|
||||||
|
* <p><Active Source> is not processed in this method but processed
|
||||||
|
* separately via {@link #processActiveSource()}.
|
||||||
|
*
|
||||||
|
* @param address logical address of CEC device which the messages to process
|
||||||
|
* are associated with
|
||||||
|
*/
|
||||||
|
void processMessagesForDevice(int address) {
|
||||||
|
HdmiLogger.debug("Processing message for address:" + address);
|
||||||
|
for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) {
|
||||||
|
HdmiCecMessage message = iter.next();
|
||||||
|
if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (message.getSource() == address) {
|
||||||
|
mDevice.onMessage(message);
|
||||||
|
HdmiLogger.debug("Processing message:" + message);
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process <Active Source>.
|
||||||
|
*
|
||||||
|
* <p>The message has a dependency on TV input framework. Should be invoked
|
||||||
|
* after we get the callback
|
||||||
|
* {@link android.media.tv.TvInputManager.TvInputCallback#onInputAdded(String)}
|
||||||
|
* to ensure the processing of the message takes effect when transformed
|
||||||
|
* to input change callback.
|
||||||
|
*
|
||||||
|
* @param address logical address of the device to be the active source
|
||||||
|
*/
|
||||||
|
void processActiveSource(int address) {
|
||||||
|
for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) {
|
||||||
|
HdmiCecMessage message = iter.next();
|
||||||
|
if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE
|
||||||
|
&& message.getSource() == address) {
|
||||||
|
mDevice.onMessage(message);
|
||||||
|
HdmiLogger.debug("Processing message:" + message);
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -326,6 +326,8 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
|
|||||||
Slog.v(TAG, "--------------------------------------------");
|
Slog.v(TAG, "--------------------------------------------");
|
||||||
mCallback.onDeviceDiscoveryDone(result);
|
mCallback.onDeviceDiscoveryDone(result);
|
||||||
finish();
|
finish();
|
||||||
|
// Process any commands buffered while device discovery action was in progress.
|
||||||
|
tv().processAllDelayedMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAndProceedStage() {
|
private void checkAndProceedStage() {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANAL
|
|||||||
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
|
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
|
||||||
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
|
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.content.Context;
|
||||||
import android.hardware.hdmi.HdmiControlManager;
|
import android.hardware.hdmi.HdmiControlManager;
|
||||||
import android.hardware.hdmi.HdmiDeviceInfo;
|
import android.hardware.hdmi.HdmiDeviceInfo;
|
||||||
import android.hardware.hdmi.HdmiRecordSources;
|
import android.hardware.hdmi.HdmiRecordSources;
|
||||||
@@ -37,6 +39,9 @@ import android.hardware.hdmi.HdmiTimerRecordSources;
|
|||||||
import android.hardware.hdmi.IHdmiControlCallback;
|
import android.hardware.hdmi.IHdmiControlCallback;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.AudioSystem;
|
import android.media.AudioSystem;
|
||||||
|
import android.media.tv.TvInputInfo;
|
||||||
|
import android.media.tv.TvInputManager;
|
||||||
|
import android.media.tv.TvInputManager.TvInputCallback;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.SystemProperties;
|
import android.os.SystemProperties;
|
||||||
import android.provider.Settings.Global;
|
import android.provider.Settings.Global;
|
||||||
@@ -49,6 +54,7 @@ import com.android.internal.util.IndentingPrintWriter;
|
|||||||
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
|
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
|
||||||
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
|
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
|
||||||
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
|
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
|
||||||
|
import com.android.server.SystemService;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -123,6 +129,26 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
// other CEC devices since they might not have logical address.
|
// other CEC devices since they might not have logical address.
|
||||||
private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
|
private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
|
||||||
|
|
||||||
|
// Message buffer used to buffer selected messages to process later. <Active Source>
|
||||||
|
// from a source device, for instance, needs to be buffered if the device is not
|
||||||
|
// discovered yet. The buffered commands are taken out and when they are ready to
|
||||||
|
// handle.
|
||||||
|
private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
|
||||||
|
|
||||||
|
// Defines the callback invoked when TV input framework is updated with input status.
|
||||||
|
// We are interested in the notification for HDMI input addition event, in order to
|
||||||
|
// process any CEC commands that arrived before the input is added.
|
||||||
|
private final TvInputCallback mTvInputCallback = new TvInputCallback() {
|
||||||
|
@Override
|
||||||
|
public void onInputAdded(String inputId) {
|
||||||
|
TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
|
||||||
|
HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
|
||||||
|
if (info != null && info.isCecDevice()) {
|
||||||
|
mDelayedMessageBuffer.processActiveSource(info.getLogicalAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
HdmiCecLocalDeviceTv(HdmiControlService service) {
|
HdmiCecLocalDeviceTv(HdmiControlService service) {
|
||||||
super(service, HdmiDeviceInfo.DEVICE_TV);
|
super(service, HdmiDeviceInfo.DEVICE_TV);
|
||||||
mPrevPortId = Constants.INVALID_PORT_ID;
|
mPrevPortId = Constants.INVALID_PORT_ID;
|
||||||
@@ -136,6 +162,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
@ServiceThreadOnly
|
@ServiceThreadOnly
|
||||||
protected void onAddressAllocated(int logicalAddress, int reason) {
|
protected void onAddressAllocated(int logicalAddress, int reason) {
|
||||||
assertRunOnServiceThread();
|
assertRunOnServiceThread();
|
||||||
|
mService.registerTvInputCallback(mTvInputCallback);
|
||||||
mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
|
mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
|
||||||
mAddress, mService.getPhysicalAddress(), mDeviceType));
|
mAddress, mService.getPhysicalAddress(), mDeviceType));
|
||||||
mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
|
mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
|
||||||
@@ -407,7 +434,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
|
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
|
||||||
HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
|
HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
handleNewDeviceAtTheTailOfActivePath(physicalAddress);
|
if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
|
||||||
|
mDelayedMessageBuffer.add(message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
|
ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
|
||||||
ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
|
ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
|
||||||
@@ -555,7 +584,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
activeSource.physicalAddress, deviceType));
|
activeSource.physicalAddress, deviceType));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleNewDeviceAtTheTailOfActivePath(int path) {
|
private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
|
||||||
// Seq #22
|
// Seq #22
|
||||||
if (isTailOfActivePath(path, getActivePath())) {
|
if (isTailOfActivePath(path, getActivePath())) {
|
||||||
removeAction(RoutingControlAction.class);
|
removeAction(RoutingControlAction.class);
|
||||||
@@ -564,7 +593,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
|
mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
|
||||||
mAddress, getActivePath(), newPath));
|
mAddress, getActivePath(), newPath));
|
||||||
addAndStartAction(new RoutingControlAction(this, newPath, false, null));
|
addAndStartAction(new RoutingControlAction(this, newPath, false, null));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -706,8 +737,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
@ServiceThreadOnly
|
@ServiceThreadOnly
|
||||||
void onNewAvrAdded(HdmiDeviceInfo avr) {
|
void onNewAvrAdded(HdmiDeviceInfo avr) {
|
||||||
assertRunOnServiceThread();
|
assertRunOnServiceThread();
|
||||||
|
if (getSystemAudioModeSetting()) {
|
||||||
addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
|
addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
|
||||||
if (isArcFeatureEnabled()) {
|
}
|
||||||
|
if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) {
|
||||||
startArcAction(true);
|
startArcAction(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -941,6 +974,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
assertRunOnServiceThread();
|
assertRunOnServiceThread();
|
||||||
|
|
||||||
if (!canStartArcUpdateAction(message.getSource(), true)) {
|
if (!canStartArcUpdateAction(message.getSource(), true)) {
|
||||||
|
if (getAvrDeviceInfo() == null) {
|
||||||
|
// AVR may not have been discovered yet. Delay the message processing.
|
||||||
|
mDelayedMessageBuffer.add(message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
|
||||||
if (!isConnectedToArcPort(message.getSource())) {
|
if (!isConnectedToArcPort(message.getSource())) {
|
||||||
displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
|
displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
|
||||||
@@ -1436,6 +1474,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
|
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
|
||||||
super.disableDevice(initiatedByCec, callback);
|
super.disableDevice(initiatedByCec, callback);
|
||||||
assertRunOnServiceThread();
|
assertRunOnServiceThread();
|
||||||
|
mService.unregisterTvInputCallback(mTvInputCallback);
|
||||||
// Remove any repeated working actions.
|
// Remove any repeated working actions.
|
||||||
// HotplugDetectionAction will be reinstated during the wake up process.
|
// HotplugDetectionAction will be reinstated during the wake up process.
|
||||||
// HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
|
// HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
|
||||||
@@ -1714,6 +1753,18 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
|
|||||||
mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
|
mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ServiceThreadOnly
|
||||||
|
void processAllDelayedMessages() {
|
||||||
|
assertRunOnServiceThread();
|
||||||
|
mDelayedMessageBuffer.processAllMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServiceThreadOnly
|
||||||
|
void processDelayedMessages(int address) {
|
||||||
|
assertRunOnServiceThread();
|
||||||
|
mDelayedMessageBuffer.processMessagesForDevice(address);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dump(final IndentingPrintWriter pw) {
|
protected void dump(final IndentingPrintWriter pw) {
|
||||||
super.dump(pw);
|
super.dump(pw);
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ import android.hardware.hdmi.IHdmiRecordListener;
|
|||||||
import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
|
import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
|
||||||
import android.hardware.hdmi.IHdmiVendorCommandListener;
|
import android.hardware.hdmi.IHdmiVendorCommandListener;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
|
import android.media.tv.TvInputManager;
|
||||||
|
import android.media.tv.TvInputManager.TvInputCallback;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -272,6 +274,9 @@ public final class HdmiControlService extends SystemService {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private HdmiMhlControllerStub mMhlController;
|
private HdmiMhlControllerStub mMhlController;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private TvInputManager mTvInputManager;
|
||||||
|
|
||||||
// Last input port before switching to the MHL port. Should switch back to this port
|
// Last input port before switching to the MHL port. Should switch back to this port
|
||||||
// when the mobile device sends the request one touch play with off.
|
// when the mobile device sends the request one touch play with off.
|
||||||
// Gets invalidated if we go to other port/input.
|
// Gets invalidated if we go to other port/input.
|
||||||
@@ -343,6 +348,28 @@ public final class HdmiControlService extends SystemService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBootPhase(int phase) {
|
||||||
|
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
|
||||||
|
mTvInputManager = (TvInputManager) getContext().getSystemService(
|
||||||
|
Context.TV_INPUT_SERVICE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TvInputManager getTvInputManager() {
|
||||||
|
return mTvInputManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerTvInputCallback(TvInputCallback callback) {
|
||||||
|
if (mTvInputManager == null) return;
|
||||||
|
mTvInputManager.registerCallback(callback, mHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregisterTvInputCallback(TvInputCallback callback) {
|
||||||
|
if (mTvInputManager == null) return;
|
||||||
|
mTvInputManager.unregisterCallback(callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the initialization of local devices is complete.
|
* Called when the initialization of local devices is complete.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -161,6 +161,9 @@ final class NewDeviceAction extends HdmiCecFeatureAction {
|
|||||||
mDeviceType, mVendorId, mDisplayName);
|
mDeviceType, mVendorId, mDisplayName);
|
||||||
tv().addCecDevice(deviceInfo);
|
tv().addCecDevice(deviceInfo);
|
||||||
|
|
||||||
|
// Consume CEC messages we already got for this newly found device.
|
||||||
|
tv().processDelayedMessages(mDeviceLogicalAddress);
|
||||||
|
|
||||||
if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress)
|
if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress)
|
||||||
== HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
|
== HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
|
||||||
tv().onNewAvrAdded(deviceInfo);
|
tv().onNewAvrAdded(deviceInfo);
|
||||||
|
|||||||
Reference in New Issue
Block a user