1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.hdmi;
18 
19 import android.stats.hdmi.HdmiStatsEnums;
20 
21 import com.android.internal.annotations.VisibleForTesting;
22 import com.android.internal.util.FrameworkStatsLog;
23 
24 /**
25  * Provides methods for writing HDMI-CEC statsd atoms.
26  */
27 @VisibleForTesting
28 public class HdmiCecAtomWriter {
29 
30     @VisibleForTesting
31     protected static final int FEATURE_ABORT_OPCODE_UNKNOWN = 0x100;
32     private static final int ERROR_CODE_UNKNOWN = -1;
33 
34     /**
35      * Writes a HdmiCecMessageReported atom representing an HDMI CEC message.
36      * Should only be directly used for sent messages; for received messages,
37      * use the overloaded version with the errorCode argument omitted.
38      *
39      * @param message      The HDMI CEC message
40      * @param direction    Whether the message is incoming, outgoing, or neither
41      * @param errorCode    The error code from the final attempt to send the message
42      * @param callingUid   The calling uid of the app that triggered this message
43      */
messageReported( HdmiCecMessage message, int direction, int callingUid, int errorCode)44     public void messageReported(
45             HdmiCecMessage message, int direction, int callingUid, int errorCode) {
46         MessageReportedGenericArgs genericArgs = createMessageReportedGenericArgs(
47                 message, direction, errorCode, callingUid);
48         MessageReportedSpecialArgs specialArgs = createMessageReportedSpecialArgs(message);
49         messageReportedBase(genericArgs, specialArgs);
50     }
51 
52     /**
53      * Version of messageReported for received messages, where no error code is present.
54      *
55      * @param message      The HDMI CEC message
56      * @param direction    Whether the message is incoming, outgoing, or neither
57      * @param callingUid   The calling uid of the app that triggered this message
58      */
messageReported(HdmiCecMessage message, int direction, int callingUid)59     public void messageReported(HdmiCecMessage message, int direction, int callingUid) {
60         messageReported(message, direction, callingUid, ERROR_CODE_UNKNOWN);
61     }
62 
63     /**
64      * Constructs the generic arguments for logging a HDMI CEC message.
65      *
66      * @param message      The HDMI CEC message
67      * @param direction    Whether the message is incoming, outgoing, or neither
68      * @param errorCode    The error code of the message if it's outgoing;
69      *                     otherwise, ERROR_CODE_UNKNOWN
70      */
createMessageReportedGenericArgs( HdmiCecMessage message, int direction, int errorCode, int callingUid)71     private MessageReportedGenericArgs createMessageReportedGenericArgs(
72             HdmiCecMessage message, int direction, int errorCode, int callingUid) {
73         int sendMessageResult = errorCode == ERROR_CODE_UNKNOWN
74                 ? HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN
75                 : errorCode + 10;
76         return new MessageReportedGenericArgs(callingUid, direction, message.getSource(),
77                 message.getDestination(), message.getOpcode(), sendMessageResult);
78     }
79 
80     /**
81      * Constructs the special arguments for logging an HDMI CEC message.
82      *
83      * @param message The HDMI CEC message to log
84      * @return An object containing the special arguments for the message
85      */
createMessageReportedSpecialArgs(HdmiCecMessage message)86     private MessageReportedSpecialArgs createMessageReportedSpecialArgs(HdmiCecMessage message) {
87         // Special arguments depend on message opcode
88         switch (message.getOpcode()) {
89             case Constants.MESSAGE_USER_CONTROL_PRESSED:
90                 return createUserControlPressedSpecialArgs(message);
91             case Constants.MESSAGE_FEATURE_ABORT:
92                 return createFeatureAbortSpecialArgs(message);
93             default:
94                 return new MessageReportedSpecialArgs();
95         }
96     }
97 
98     /**
99      * Constructs the special arguments for a <User Control Pressed> message.
100      *
101      * @param message The HDMI CEC message to log
102      */
createUserControlPressedSpecialArgs( HdmiCecMessage message)103     private MessageReportedSpecialArgs createUserControlPressedSpecialArgs(
104             HdmiCecMessage message) {
105         MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
106 
107         if (message.getParams().length > 0) {
108             int keycode = message.getParams()[0];
109             if (keycode >= 0x1E && keycode <= 0x29) {
110                 specialArgs.mUserControlPressedCommand = HdmiStatsEnums.NUMBER;
111             } else {
112                 specialArgs.mUserControlPressedCommand = keycode + 0x100;
113             }
114         }
115 
116         return specialArgs;
117     }
118 
119     /**
120      * Constructs the special arguments for a <Feature Abort> message.
121      *
122      * @param message The HDMI CEC message to log
123      */
createFeatureAbortSpecialArgs(HdmiCecMessage message)124     private MessageReportedSpecialArgs createFeatureAbortSpecialArgs(HdmiCecMessage message) {
125         MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
126 
127         if (message.getParams().length > 0) {
128             specialArgs.mFeatureAbortOpcode = message.getParams()[0] & 0xFF; // Unsigned byte
129             if (message.getParams().length > 1) {
130                 specialArgs.mFeatureAbortReason = message.getParams()[1] + 10;
131             }
132         }
133 
134         return specialArgs;
135     }
136 
137     /**
138      * Writes a HdmiCecMessageReported atom.
139      *
140      * @param genericArgs Generic arguments; shared by all HdmiCecMessageReported atoms
141      * @param specialArgs Special arguments; depends on the opcode of the message
142      */
messageReportedBase(MessageReportedGenericArgs genericArgs, MessageReportedSpecialArgs specialArgs)143     private void messageReportedBase(MessageReportedGenericArgs genericArgs,
144             MessageReportedSpecialArgs specialArgs) {
145         writeHdmiCecMessageReportedAtom(
146                 genericArgs.mUid,
147                 genericArgs.mDirection,
148                 genericArgs.mInitiatorLogicalAddress,
149                 genericArgs.mDestinationLogicalAddress,
150                 genericArgs.mOpcode,
151                 genericArgs.mSendMessageResult,
152                 specialArgs.mUserControlPressedCommand,
153                 specialArgs.mFeatureAbortOpcode,
154                 specialArgs.mFeatureAbortReason);
155     }
156 
157     /**
158      * Writes a HdmiCecMessageReported atom representing an incoming or outgoing HDMI-CEC message.
159      */
160     @VisibleForTesting
writeHdmiCecMessageReportedAtom(int uid, int direction, int initiatorLogicalAddress, int destinationLogicalAddress, int opcode, int sendMessageResult, int userControlPressedCommand, int featureAbortOpcode, int featureAbortReason)161     protected void writeHdmiCecMessageReportedAtom(int uid, int direction,
162             int initiatorLogicalAddress, int destinationLogicalAddress, int opcode,
163             int sendMessageResult, int userControlPressedCommand, int featureAbortOpcode,
164             int featureAbortReason) {
165         FrameworkStatsLog.write(
166                 FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED,
167                 uid,
168                 direction,
169                 initiatorLogicalAddress,
170                 destinationLogicalAddress,
171                 opcode,
172                 sendMessageResult,
173                 userControlPressedCommand,
174                 featureAbortOpcode,
175                 featureAbortReason);
176     }
177 
178     /**
179      * Writes a HdmiCecActiveSourceChanged atom representing a change in the active source.
180      *
181      * @param logicalAddress             The Logical Address of the new active source
182      * @param physicalAddress            The Physical Address of the new active source
183      * @param relationshipToActiveSource The relationship between this device and the active source
184      */
activeSourceChanged(int logicalAddress, int physicalAddress, @Constants.PathRelationship int relationshipToActiveSource)185     public void activeSourceChanged(int logicalAddress, int physicalAddress,
186             @Constants.PathRelationship int relationshipToActiveSource) {
187         FrameworkStatsLog.write(
188                 FrameworkStatsLog.HDMI_CEC_ACTIVE_SOURCE_CHANGED,
189                 logicalAddress,
190                 physicalAddress,
191                 relationshipToActiveSource
192         );
193     }
194 
195     /**
196      * Contains the required arguments for creating any HdmiCecMessageReported atom
197      */
198     private class MessageReportedGenericArgs {
199         final int mUid;
200         final int mDirection;
201         final int mInitiatorLogicalAddress;
202         final int mDestinationLogicalAddress;
203         final int mOpcode;
204         final int mSendMessageResult;
205 
MessageReportedGenericArgs(int uid, int direction, int initiatorLogicalAddress, int destinationLogicalAddress, int opcode, int sendMessageResult)206         MessageReportedGenericArgs(int uid, int direction, int initiatorLogicalAddress,
207                 int destinationLogicalAddress, int opcode, int sendMessageResult) {
208             this.mUid = uid;
209             this.mDirection = direction;
210             this.mInitiatorLogicalAddress = initiatorLogicalAddress;
211             this.mDestinationLogicalAddress = destinationLogicalAddress;
212             this.mOpcode = opcode;
213             this.mSendMessageResult = sendMessageResult;
214         }
215     }
216 
217     /**
218      * Contains the opcode-dependent arguments for creating a HdmiCecMessageReported atom. Each
219      * field is initialized to a null-like value by default. Therefore, a freshly constructed
220      * instance of this object represents a HDMI CEC message whose type does not require any
221      * additional arguments.
222      */
223     private class MessageReportedSpecialArgs {
224         int mUserControlPressedCommand = HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN;
225         int mFeatureAbortOpcode = FEATURE_ABORT_OPCODE_UNKNOWN;
226         int mFeatureAbortReason = HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN;
227     }
228 }
229