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 static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
20 import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED;
21 import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
22 import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_IDLE;
23 
24 import android.stats.hdmi.HdmiStatsEnums;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.internal.util.FrameworkStatsLog;
28 
29 /**
30  * Provides methods for writing HDMI-CEC statsd atoms.
31  */
32 @VisibleForTesting
33 public class HdmiCecAtomWriter {
34 
35     @VisibleForTesting
36     protected static final int FEATURE_ABORT_OPCODE_UNKNOWN = 0x100;
37     private static final int ERROR_CODE_UNKNOWN = -1;
38 
39     /**
40      * Writes a HdmiCecMessageReported atom representing an HDMI CEC message.
41      * Should only be directly used for sent messages; for received messages,
42      * use the overloaded version with the errorCode argument omitted.
43      *
44      * @param message      The HDMI CEC message
45      * @param direction    Whether the message is incoming, outgoing, or neither
46      * @param errorCode    The error code from the final attempt to send the message
47      * @param callingUid   The calling uid of the app that triggered this message
48      */
messageReported( HdmiCecMessage message, int direction, int callingUid, int errorCode)49     public void messageReported(
50             HdmiCecMessage message, int direction, int callingUid, int errorCode) {
51         MessageReportedGenericArgs genericArgs = createMessageReportedGenericArgs(
52                 message, direction, errorCode, callingUid);
53         MessageReportedSpecialArgs specialArgs = createMessageReportedSpecialArgs(message);
54         messageReportedBase(genericArgs, specialArgs);
55     }
56 
57     /**
58      * Version of messageReported for received messages, where no error code is present.
59      *
60      * @param message      The HDMI CEC message
61      * @param direction    Whether the message is incoming, outgoing, or neither
62      * @param callingUid   The calling uid of the app that triggered this message
63      */
messageReported(HdmiCecMessage message, int direction, int callingUid)64     public void messageReported(HdmiCecMessage message, int direction, int callingUid) {
65         messageReported(message, direction, callingUid, ERROR_CODE_UNKNOWN);
66     }
67 
68     /**
69      * Constructs the generic arguments for logging a HDMI CEC message.
70      *
71      * @param message      The HDMI CEC message
72      * @param direction    Whether the message is incoming, outgoing, or neither
73      * @param errorCode    The error code of the message if it's outgoing;
74      *                     otherwise, ERROR_CODE_UNKNOWN
75      */
createMessageReportedGenericArgs( HdmiCecMessage message, int direction, int errorCode, int callingUid)76     private MessageReportedGenericArgs createMessageReportedGenericArgs(
77             HdmiCecMessage message, int direction, int errorCode, int callingUid) {
78         int sendMessageResult = errorCode == ERROR_CODE_UNKNOWN
79                 ? HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN
80                 : errorCode + 10;
81         return new MessageReportedGenericArgs(callingUid, direction, message.getSource(),
82                 message.getDestination(), message.getOpcode(), sendMessageResult);
83     }
84 
85     /**
86      * Constructs the special arguments for logging an HDMI CEC message.
87      *
88      * @param message The HDMI CEC message to log
89      * @return An object containing the special arguments for the message
90      */
createMessageReportedSpecialArgs(HdmiCecMessage message)91     private MessageReportedSpecialArgs createMessageReportedSpecialArgs(HdmiCecMessage message) {
92         // Special arguments depend on message opcode
93         switch (message.getOpcode()) {
94             case Constants.MESSAGE_USER_CONTROL_PRESSED:
95                 return createUserControlPressedSpecialArgs(message);
96             case Constants.MESSAGE_FEATURE_ABORT:
97                 return createFeatureAbortSpecialArgs(message);
98             default:
99                 return new MessageReportedSpecialArgs();
100         }
101     }
102 
103     /**
104      * Constructs the special arguments for a <User Control Pressed> message.
105      *
106      * @param message The HDMI CEC message to log
107      */
createUserControlPressedSpecialArgs( HdmiCecMessage message)108     private MessageReportedSpecialArgs createUserControlPressedSpecialArgs(
109             HdmiCecMessage message) {
110         MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
111 
112         if (message.getParams().length > 0) {
113             int keycode = message.getParams()[0];
114             if (keycode >= 0x1E && keycode <= 0x29) {
115                 specialArgs.mUserControlPressedCommand = HdmiStatsEnums.NUMBER;
116             } else {
117                 specialArgs.mUserControlPressedCommand = keycode + 0x100;
118             }
119         }
120 
121         return specialArgs;
122     }
123 
124     /**
125      * Constructs the special arguments for a <Feature Abort> message.
126      *
127      * @param message The HDMI CEC message to log
128      */
createFeatureAbortSpecialArgs(HdmiCecMessage message)129     private MessageReportedSpecialArgs createFeatureAbortSpecialArgs(HdmiCecMessage message) {
130         MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
131 
132         if (message.getParams().length > 0) {
133             specialArgs.mFeatureAbortOpcode = message.getParams()[0] & 0xFF; // Unsigned byte
134             if (message.getParams().length > 1) {
135                 specialArgs.mFeatureAbortReason = message.getParams()[1] + 10;
136             }
137         }
138 
139         return specialArgs;
140     }
141 
142     /**
143      * Writes a HdmiCecMessageReported atom.
144      *
145      * @param genericArgs Generic arguments; shared by all HdmiCecMessageReported atoms
146      * @param specialArgs Special arguments; depends on the opcode of the message
147      */
messageReportedBase(MessageReportedGenericArgs genericArgs, MessageReportedSpecialArgs specialArgs)148     private void messageReportedBase(MessageReportedGenericArgs genericArgs,
149             MessageReportedSpecialArgs specialArgs) {
150         writeHdmiCecMessageReportedAtom(
151                 genericArgs.mUid,
152                 genericArgs.mDirection,
153                 genericArgs.mInitiatorLogicalAddress,
154                 genericArgs.mDestinationLogicalAddress,
155                 genericArgs.mOpcode,
156                 genericArgs.mSendMessageResult,
157                 specialArgs.mUserControlPressedCommand,
158                 specialArgs.mFeatureAbortOpcode,
159                 specialArgs.mFeatureAbortReason);
160     }
161 
162     /**
163      * Writes a HdmiCecMessageReported atom representing an incoming or outgoing HDMI-CEC message.
164      */
165     @VisibleForTesting
writeHdmiCecMessageReportedAtom(int uid, int direction, int initiatorLogicalAddress, int destinationLogicalAddress, int opcode, int sendMessageResult, int userControlPressedCommand, int featureAbortOpcode, int featureAbortReason)166     protected void writeHdmiCecMessageReportedAtom(int uid, int direction,
167             int initiatorLogicalAddress, int destinationLogicalAddress, int opcode,
168             int sendMessageResult, int userControlPressedCommand, int featureAbortOpcode,
169             int featureAbortReason) {
170         FrameworkStatsLog.write(
171                 FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED,
172                 uid,
173                 direction,
174                 initiatorLogicalAddress,
175                 destinationLogicalAddress,
176                 opcode,
177                 sendMessageResult,
178                 userControlPressedCommand,
179                 featureAbortOpcode,
180                 featureAbortReason);
181     }
182 
183     /**
184      * Writes a HdmiCecActiveSourceChanged atom representing a change in the active source.
185      *
186      * @param logicalAddress             The Logical Address of the new active source
187      * @param physicalAddress            The Physical Address of the new active source
188      * @param relationshipToActiveSource The relationship between this device and the active source
189      */
activeSourceChanged(int logicalAddress, int physicalAddress, @Constants.PathRelationship int relationshipToActiveSource)190     public void activeSourceChanged(int logicalAddress, int physicalAddress,
191             @Constants.PathRelationship int relationshipToActiveSource) {
192         FrameworkStatsLog.write(
193                 FrameworkStatsLog.HDMI_CEC_ACTIVE_SOURCE_CHANGED,
194                 logicalAddress,
195                 physicalAddress,
196                 relationshipToActiveSource
197         );
198     }
199 
200     /**
201      * Writes a HdmiEarcStatusReported atom representing a eARC status change.
202      * @param isSupported         Whether the hardware supports eARC.
203      * @param isEnabled           Whether eARC is enabled.
204      * @param oldConnectionState  If enumLogReason == HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED,
205      *                            the state just before the change. Otherwise, the current state.
206      * @param newConnectionState  If enumLogReason == HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED,
207      *                            the state just after the change. Otherwise, the current state.
208      * @param enumLogReason       The event that triggered the log.
209      */
earcStatusChanged(boolean isSupported, boolean isEnabled, int oldConnectionState, int newConnectionState, int enumLogReason)210     public void earcStatusChanged(boolean isSupported, boolean isEnabled, int oldConnectionState,
211             int newConnectionState, int enumLogReason) {
212         int enumOldConnectionState = earcStateToEnum(oldConnectionState);
213         int enumNewConnectionState = earcStateToEnum(newConnectionState);
214 
215         FrameworkStatsLog.write(
216                 FrameworkStatsLog.HDMI_EARC_STATUS_REPORTED,
217                 isSupported,
218                 isEnabled,
219                 enumOldConnectionState,
220                 enumNewConnectionState,
221                 enumLogReason
222         );
223     }
224 
225     /**
226      * Writes a HdmiSoundbarModeStatusReported atom representing a Dynamic soundbar mode status
227      * change.
228      * @param isSupported         Whether the hardware supports ARC.
229      * @param isEnabled           Whether DSM is enabled.
230      * @param enumLogReason       The event that triggered the log.
231      */
dsmStatusChanged(boolean isSupported, boolean isEnabled, int enumLogReason)232     public void dsmStatusChanged(boolean isSupported, boolean isEnabled, int enumLogReason) {
233         FrameworkStatsLog.write(
234                 FrameworkStatsLog.HDMI_SOUNDBAR_MODE_STATUS_REPORTED,
235                 isSupported,
236                 isEnabled,
237                 enumLogReason);
238     }
239 
earcStateToEnum(int earcState)240     private int earcStateToEnum(int earcState) {
241         switch (earcState) {
242             case HDMI_EARC_STATUS_IDLE:
243                 return HdmiStatsEnums.HDMI_EARC_STATUS_IDLE;
244             case HDMI_EARC_STATUS_EARC_PENDING:
245                 return HdmiStatsEnums.HDMI_EARC_STATUS_EARC_PENDING;
246             case HDMI_EARC_STATUS_ARC_PENDING:
247                 return HdmiStatsEnums.HDMI_EARC_STATUS_ARC_PENDING;
248             case HDMI_EARC_STATUS_EARC_CONNECTED:
249                 return HdmiStatsEnums.HDMI_EARC_STATUS_EARC_CONNECTED;
250             default:
251                 return HdmiStatsEnums.HDMI_EARC_STATUS_UNKNOWN;
252         }
253     }
254 
255     /**
256      * Contains the required arguments for creating any HdmiCecMessageReported atom
257      */
258     private class MessageReportedGenericArgs {
259         final int mUid;
260         final int mDirection;
261         final int mInitiatorLogicalAddress;
262         final int mDestinationLogicalAddress;
263         final int mOpcode;
264         final int mSendMessageResult;
265 
MessageReportedGenericArgs(int uid, int direction, int initiatorLogicalAddress, int destinationLogicalAddress, int opcode, int sendMessageResult)266         MessageReportedGenericArgs(int uid, int direction, int initiatorLogicalAddress,
267                 int destinationLogicalAddress, int opcode, int sendMessageResult) {
268             this.mUid = uid;
269             this.mDirection = direction;
270             this.mInitiatorLogicalAddress = initiatorLogicalAddress;
271             this.mDestinationLogicalAddress = destinationLogicalAddress;
272             this.mOpcode = opcode;
273             this.mSendMessageResult = sendMessageResult;
274         }
275     }
276 
277     /**
278      * Contains the opcode-dependent arguments for creating a HdmiCecMessageReported atom. Each
279      * field is initialized to a null-like value by default. Therefore, a freshly constructed
280      * instance of this object represents a HDMI CEC message whose type does not require any
281      * additional arguments.
282      */
283     private class MessageReportedSpecialArgs {
284         int mUserControlPressedCommand = HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN;
285         int mFeatureAbortOpcode = FEATURE_ABORT_OPCODE_UNKNOWN;
286         int mFeatureAbortReason = HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN;
287     }
288 }
289