Implement initial statsd atoms for HDMI-CEC

Test: manual using ./out/host/linux-x86/bin/statsd_testdrive
Bug: 159796477

Change-Id: I942661c2e8a876b9818f31ed9d8855f209dce088
This commit is contained in:
Yan Han
2020-09-11 16:56:09 +02:00
parent 657867fdaf
commit 597230a5a0
12 changed files with 283 additions and 38 deletions

View File

@@ -0,0 +1,192 @@
/*
* Copyright (C) 2020 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.stats.hdmi.HdmiStatsEnums;
import com.android.internal.util.FrameworkStatsLog;
class HdmiCecAtomWriter {
private static final int FEATURE_ABORT_OPCODE_UNKNOWN = 0x100;
private static final int ERROR_CODE_UNKNOWN = -1;
/**
* Writes a HdmiCecMessageReported atom representing an HDMI CEC message.
* Should only be directly used for sent messages; for received messages,
* use the overloaded version with the errorCode argument omitted.
*
* @param message The HDMI CEC message
* @param direction Whether the message is incoming, outgoing, or neither
* @param errorCode The error code from the final attempt to send the message
*/
public void messageReported(HdmiCecMessage message, int direction, int errorCode) {
MessageReportedGenericArgs genericArgs = createMessageReportedGenericArgs(
message, direction, errorCode);
MessageReportedSpecialArgs specialArgs = createMessageReportedSpecialArgs(message);
messageReportedBase(genericArgs, specialArgs);
}
/**
* Version of messageReported for received messages, where no error code is present.
*
* @param message The HDMI CEC message
* @param direction Whether the message is incoming, outgoing, or neither
*/
public void messageReported(HdmiCecMessage message, int direction) {
messageReported(message, direction, ERROR_CODE_UNKNOWN);
}
/**
* Constructs the generic arguments for logging a HDMI CEC message.
*
* @param message The HDMI CEC message
* @param direction Whether the message is incoming, outgoing, or neither
* @param errorCode The error code of the message if it's outgoing;
* otherwise, ERROR_CODE_UNKNOWN
*/
private MessageReportedGenericArgs createMessageReportedGenericArgs(
HdmiCecMessage message, int direction, int errorCode) {
int sendMessageResult = errorCode == ERROR_CODE_UNKNOWN
? HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN
: errorCode + 10;
return new MessageReportedGenericArgs(direction, message.getSource(),
message.getDestination(), message.getOpcode(), sendMessageResult);
}
/**
* Constructs the special arguments for logging an HDMI CEC message.
*
* @param message The HDMI CEC message to log
* @return An object containing the special arguments for the message
*/
private MessageReportedSpecialArgs createMessageReportedSpecialArgs(HdmiCecMessage message) {
// Special arguments depend on message opcode
switch (message.getOpcode()) {
case Constants.MESSAGE_USER_CONTROL_PRESSED:
return createUserControlPressedSpecialArgs(message);
case Constants.MESSAGE_FEATURE_ABORT:
return createFeatureAbortSpecialArgs(message);
default:
return new MessageReportedSpecialArgs();
}
}
/**
* Constructs the special arguments for a <User Control Pressed> message.
*
* @param message The HDMI CEC message to log
*/
private MessageReportedSpecialArgs createUserControlPressedSpecialArgs(
HdmiCecMessage message) {
MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
int keycode = message.getParams()[0];
if (keycode >= 0x1E && keycode <= 0x29) {
specialArgs.mUserControlPressedCommand = HdmiStatsEnums.NUMBER;
} else {
specialArgs.mUserControlPressedCommand = keycode + 0x100;
}
return specialArgs;
}
/**
* Constructs method for constructing the special arguments for a <Feature Abort> message.
*
* @param message The HDMI CEC message to log
*/
private MessageReportedSpecialArgs createFeatureAbortSpecialArgs(HdmiCecMessage message) {
MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
specialArgs.mFeatureAbortOpcode = message.getParams()[0] & 0xFF; // Unsigned byte
specialArgs.mFeatureAbortReason = message.getParams()[1] + 10;
return specialArgs;
}
/**
* Writes a HdmiCecMessageReported atom.
*
* @param genericArgs Generic arguments; shared by all HdmiCecMessageReported atoms
* @param specialArgs Special arguments; depends on the opcode of the message
*/
private void messageReportedBase(MessageReportedGenericArgs genericArgs,
MessageReportedSpecialArgs specialArgs) {
FrameworkStatsLog.write(
FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED,
0, // Placeholder field
genericArgs.mDirection,
genericArgs.mInitiatorLogicalAddress,
genericArgs.mDestinationLogicalAddress,
genericArgs.mOpcode,
genericArgs.mSendMessageResult,
specialArgs.mUserControlPressedCommand,
specialArgs.mFeatureAbortOpcode,
specialArgs.mFeatureAbortReason);
}
/**
* Writes a HdmiCecActiveSourceChanged atom representing a change in the active source.
*
* @param logicalAddress The Logical Address of the new active source
* @param physicalAddress The Physical Address of the new active source
* @param relationshipToActiveSource The relationship between this device and the active source
*/
public void activeSourceChanged(int logicalAddress, int physicalAddress,
@Constants.PathRelationship int relationshipToActiveSource) {
FrameworkStatsLog.write(
FrameworkStatsLog.HDMI_CEC_ACTIVE_SOURCE_CHANGED,
logicalAddress,
physicalAddress,
relationshipToActiveSource
);
}
/**
* Contains the required arguments for creating any HdmiCecMessageReported atom
*/
private class MessageReportedGenericArgs {
final int mDirection;
final int mInitiatorLogicalAddress;
final int mDestinationLogicalAddress;
final int mOpcode;
final int mSendMessageResult;
MessageReportedGenericArgs(int direction, int initiatorLogicalAddress,
int destinationLogicalAddress, int opcode, int sendMessageResult) {
this.mDirection = direction;
this.mInitiatorLogicalAddress = initiatorLogicalAddress;
this.mDestinationLogicalAddress = destinationLogicalAddress;
this.mOpcode = opcode;
this.mSendMessageResult = sendMessageResult;
}
}
/**
* Contains the opcode-dependent arguments for creating a HdmiCecMessageReported atom. Each
* field is initialized to a null-like value by default. Therefore, a freshly constructed
* instance of this object represents a HDMI CEC message whose type does not require any
* additional arguments.
*/
private class MessageReportedSpecialArgs {
int mUserControlPressedCommand = HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN;
int mFeatureAbortOpcode = FEATURE_ABORT_OPCODE_UNKNOWN;
int mFeatureAbortReason = HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN;
}
}

View File

@@ -28,9 +28,11 @@ import android.os.Handler;
import android.os.IHwBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.stats.hdmi.HdmiStatsEnums;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -123,10 +125,14 @@ final class HdmiCecController {
private final NativeWrapper mNativeWrapperImpl;
private final HdmiCecAtomWriter mHdmiCecAtomWriter;
// Private constructor. Use HdmiCecController.create().
private HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper) {
private HdmiCecController(
HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
mService = service;
mNativeWrapperImpl = nativeWrapper;
mHdmiCecAtomWriter = atomWriter;
}
/**
@@ -134,21 +140,22 @@ final class HdmiCecController {
* inner device or has no device it will return {@code null}.
*
* <p>Declared as package-private, accessed by {@link HdmiControlService} only.
* @param service {@link HdmiControlService} instance used to create internal handler
* and to pass callback for incoming message or event.
* @param service {@link HdmiControlService} instance used to create internal handler
* and to pass callback for incoming message or event.
* @param atomWriter {@link HdmiCecAtomWriter} instance for writing atoms for metrics.
* @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
* returns {@code null}.
*/
static HdmiCecController create(HdmiControlService service) {
return createWithNativeWrapper(service, new NativeWrapperImpl());
static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) {
return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter);
}
/**
* A factory method with injection of native methods for testing.
*/
static HdmiCecController createWithNativeWrapper(
HdmiControlService service, NativeWrapper nativeWrapper) {
HdmiCecController controller = new HdmiCecController(service, nativeWrapper);
HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
HdmiCecController controller = new HdmiCecController(service, nativeWrapper, atomWriter);
String nativePtr = nativeWrapper.nativeInit();
if (nativePtr == null) {
HdmiLogger.warning("Couldn't get tv.cec service.");
@@ -619,7 +626,7 @@ final class HdmiCecController {
public void run() {
HdmiLogger.debug("[S]:" + cecMessage);
byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
int i = 0;
int retransmissionCount = 0;
int errorCode = SendMessageResult.SUCCESS;
do {
errorCode = mNativeWrapperImpl.nativeSendCecCommand(
@@ -627,20 +634,25 @@ final class HdmiCecController {
if (errorCode == SendMessageResult.SUCCESS) {
break;
}
} while (i++ < HdmiConfig.RETRANSMISSION_COUNT);
} while (retransmissionCount++ < HdmiConfig.RETRANSMISSION_COUNT);
final int finalError = errorCode;
if (finalError != SendMessageResult.SUCCESS) {
Slog.w(TAG, "Failed to send " + cecMessage + " with errorCode=" + finalError);
}
if (callback != null) {
runOnServiceThread(new Runnable() {
@Override
public void run() {
runOnServiceThread(new Runnable() {
@Override
public void run() {
mHdmiCecAtomWriter.messageReported(
cecMessage,
FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED__DIRECTION__OUTGOING,
finalError
);
if (callback != null) {
callback.onSendCompleted(finalError);
}
});
}
}
});
}
});
}
@@ -654,9 +666,38 @@ final class HdmiCecController {
HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
HdmiLogger.debug("[R]:" + command);
addCecMessageToHistory(true /* isReceived */, command);
mHdmiCecAtomWriter.messageReported(command,
incomingMessageDirection(srcAddress, dstAddress));
onReceiveCommand(command);
}
/**
* Computes the direction of an incoming message, as implied by the source and
* destination addresses. This will usually return INCOMING; if not, it can indicate a bug.
*/
private int incomingMessageDirection(int srcAddress, int dstAddress) {
boolean sourceIsLocal = false;
boolean destinationIsLocal = false;
for (HdmiCecLocalDevice localDevice : getLocalDeviceList()) {
int logicalAddress = localDevice.getDeviceInfo().getLogicalAddress();
if (logicalAddress == srcAddress) {
sourceIsLocal = true;
}
if (logicalAddress == dstAddress) {
destinationIsLocal = true;
}
}
if (!sourceIsLocal && destinationIsLocal) {
return HdmiStatsEnums.INCOMING;
} else if (sourceIsLocal && destinationIsLocal) {
return HdmiStatsEnums.TO_SELF;
}
return HdmiStatsEnums.MESSAGE_DIRECTION_OTHER;
}
/**
* Called when a hotplug event issues.
*/

View File

@@ -397,6 +397,10 @@ public class HdmiControlService extends SystemService {
// Set to true if the logical address allocation is completed.
private boolean mAddressAllocated = false;
// Object that handles logging statsd atoms.
// Use getAtomWriter() instead of accessing directly, to allow dependency injection for testing.
private HdmiCecAtomWriter mAtomWriter = new HdmiCecAtomWriter();
// Buffer for processing the incoming cec messages while allocating logical addresses.
private final class CecMessageBuffer {
private List<HdmiCecMessage> mBuffer = new ArrayList<>();
@@ -509,7 +513,7 @@ public class HdmiControlService extends SystemService {
mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
if (mCecController == null) {
mCecController = HdmiCecController.create(this);
mCecController = HdmiCecController.create(this, getAtomWriter());
}
if (mCecController != null) {
if (mHdmiControlEnabled) {
@@ -3233,6 +3237,10 @@ public class HdmiControlService extends SystemService {
mActiveSource.logicalAddress = logicalAddress;
mActiveSource.physicalAddress = physicalAddress;
}
getAtomWriter().activeSourceChanged(logicalAddress, physicalAddress,
HdmiUtils.pathRelationship(getPhysicalAddress(), physicalAddress));
// If the current device is a source device, check if the current Active Source matches
// the local device info. Set mIsActiveSource of the local device accordingly.
for (HdmiCecLocalDevice device : getAllLocalDevices()) {
@@ -3363,6 +3371,11 @@ public class HdmiControlService extends SystemService {
}
}
@VisibleForTesting
HdmiCecAtomWriter getAtomWriter() {
return mAtomWriter;
}
boolean isMhlInputChangeEnabled() {
synchronized (mLock) {
return mMhlInputChangeEnabled;

View File

@@ -110,7 +110,7 @@ public class ActiveSourceActionTest {
mHdmiControlService.setIoLooper(looper);
mNativeWrapper = new FakeNativeWrapper();
HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
this.mHdmiControlService, mNativeWrapper);
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));

View File

@@ -137,8 +137,8 @@ public class ArcInitiationActionFromAvrTest {
Looper looper = mTestLooper.getLooper();
mHdmiControlService.setIoLooper(looper);
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController =
HdmiCecController.createWithNativeWrapper(this.mHdmiControlService, mNativeWrapper);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));

View File

@@ -89,8 +89,8 @@ public class HdmiCecControllerTest {
mMyLooper = mTestLooper.getLooper();
mHdmiControlService = new MyHdmiControlService(InstrumentationRegistry.getTargetContext());
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController =
HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
}
/** Tests for {@link HdmiCecController#allocateLogicalAddress} */

View File

@@ -188,8 +188,8 @@ public class HdmiCecLocalDeviceAudioSystemTest {
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
mNativeWrapper.setPhysicalAddress(SELF_PHYSICAL_ADDRESS);
mHdmiCecController =
HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));

View File

@@ -119,8 +119,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiCecLocalDevicePlayback.init();
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController =
HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));

View File

@@ -117,9 +117,8 @@ public class HdmiCecLocalDeviceTest {
}
};
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
mHdmiCecController =
HdmiCecController.createWithNativeWrapper(
mHdmiControlService, new FakeNativeWrapper());
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, new FakeNativeWrapper(), mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiLocalDevice = new MyHdmiCecLocalDevice(mHdmiControlService, DEVICE_TV);
mMessageValidator =

View File

@@ -97,8 +97,8 @@ public class HdmiCecLocalDeviceTvTest {
mHdmiCecLocalDeviceTv.init();
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController =
HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));

View File

@@ -15,28 +15,28 @@
*/
package com.android.server.hdmi;
import static android.os.SystemClock.sleep;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.Looper;
import android.os.SystemProperties;
import android.os.test.TestLooper;
import android.util.Slog;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
/**
* Tests for {@link HdmiControlServiceBinderAPITest} class.
*/
@@ -158,8 +158,8 @@ public class HdmiControlServiceBinderAPITest {
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController =
HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));

View File

@@ -158,8 +158,8 @@ public class HdmiControlServiceTest {
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController =
HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));