1 /*
2  * Copyright (C) 2021 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.car.rotary;
18 
19 import android.content.ComponentName;
20 import android.graphics.Rect;
21 import android.view.KeyEvent;
22 import android.view.View;
23 import android.view.accessibility.AccessibilityNodeInfo;
24 import android.view.accessibility.AccessibilityWindowInfo;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 
29 import com.android.internal.util.dump.DualDumpOutputStream;
30 
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.Map;
34 
35 /** Utility methods for dumpsys. */
36 final class DumpUtils {
DumpUtils()37     private DumpUtils() {}
38 
39     /** Writes {@code focusDirection} to a dump in text or proto format. */
writeFocusDirection(@onNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, @NonNull String fieldName, long fieldId, @View.FocusRealDirection int focusDirection)40     static void writeFocusDirection(@NonNull DualDumpOutputStream dumpOutputStream,
41             boolean dumpAsProto, @NonNull String fieldName, long fieldId,
42             @View.FocusRealDirection int focusDirection) {
43         if (!dumpAsProto) {
44             dumpOutputStream.write(fieldName, fieldId, Navigator.directionToString(focusDirection));
45             return;
46         }
47         int val;
48         switch (focusDirection) {
49             case View.FOCUS_LEFT:
50                 val = RotaryProtos.FOCUS_LEFT;
51                 break;
52             case View.FOCUS_UP:
53                 val = RotaryProtos.FOCUS_UP;
54                 break;
55             case View.FOCUS_RIGHT:
56                 val = RotaryProtos.FOCUS_RIGHT;
57                 break;
58             case View.FOCUS_DOWN:
59                 val = RotaryProtos.FOCUS_DOWN;
60                 break;
61             default:
62                 throw new IllegalArgumentException("Invalid direction: " + focusDirection);
63         }
64         dumpOutputStream.write(fieldName, fieldId, val);
65     }
66 
67     /** Writes {@code rect} to a dump in text or proto format. */
writeRect(@onNull DualDumpOutputStream dumpOutputStream, @NonNull Rect rect, @NonNull String fieldName, long fieldId)68     static void writeRect(@NonNull DualDumpOutputStream dumpOutputStream, @NonNull Rect rect,
69             @NonNull String fieldName, long fieldId) {
70         long fieldToken = dumpOutputStream.start(fieldName, fieldId);
71         dumpOutputStream.write("left", RotaryProtos.Rect.LEFT, rect.left);
72         dumpOutputStream.write("top", RotaryProtos.Rect.TOP, rect.top);
73         dumpOutputStream.write("right", RotaryProtos.Rect.RIGHT, rect.right);
74         dumpOutputStream.write("bottom", RotaryProtos.Rect.BOTTOM, rect.bottom);
75         dumpOutputStream.end(fieldToken);
76     }
77 
78     /** Writes {@code afterScrollAction} to a dump in text or proto format. */
writeAfterScrollAction(@onNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, @NonNull String fieldName, long fieldId, @RotaryService.AfterScrollAction int afterScrollAction)79     static void writeAfterScrollAction(@NonNull DualDumpOutputStream dumpOutputStream,
80             boolean dumpAsProto, @NonNull String fieldName, long fieldId,
81             @RotaryService.AfterScrollAction int afterScrollAction) {
82         if (!dumpAsProto) {
83             dumpOutputStream.write(fieldName, fieldId, afterScrollAction);
84             return;
85         }
86         int val;
87         switch (afterScrollAction) {
88             case RotaryService.NONE:
89                 val = RotaryProtos.AFTER_SCROLL_DO_NOTHING;
90                 break;
91             case RotaryService.FOCUS_PREVIOUS:
92                 val = RotaryProtos.AFTER_SCROLL_FOCUS_PREVIOUS;
93                 break;
94             case RotaryService.FOCUS_NEXT:
95                 val = RotaryProtos.AFTER_SCROLL_FOCUS_NEXT;
96                 break;
97             case RotaryService.FOCUS_FIRST:
98                 val = RotaryProtos.AFTER_SCROLL_FOCUS_FIRST;
99                 break;
100             case RotaryService.FOCUS_LAST:
101                 val = RotaryProtos.AFTER_SCROLL_FOCUS_LAST;
102                 break;
103             default:
104                 throw new IllegalArgumentException(
105                         "Invalid after scroll action: " + afterScrollAction);
106         }
107         dumpOutputStream.write(fieldName, fieldId, val);
108     }
109 
110     /** Writes {@code componentName} to a dump in text or proto format. */
writeComponentNameToString(@onNull DualDumpOutputStream dumpOutputStream, @NonNull String fieldName, long fieldId, @Nullable ComponentName componentName)111     static void writeComponentNameToString(@NonNull DualDumpOutputStream dumpOutputStream,
112             @NonNull String fieldName, long fieldId, @Nullable ComponentName componentName) {
113         dumpOutputStream.write(fieldName, fieldId,
114                 componentName == null ? null : componentName.flattenToShortString());
115     }
116 
117     /** Writes {@code object.toString()} to a dump in text or proto format. */
writeObject(@onNull DualDumpOutputStream dumpOutputStream, @NonNull String fieldName, long fieldId, @Nullable Object object)118     static void writeObject(@NonNull DualDumpOutputStream dumpOutputStream,
119             @NonNull String fieldName, long fieldId, @Nullable Object object) {
120         dumpOutputStream.write(fieldName, fieldId, object == null ? null : object.toString());
121     }
122 
123     /**
124      * Writes the result of {@link Object#toString} on each of {@code objects}' elements to a dump
125      * in text or proto format. In the latter case, the field must be {@code repeated}.
126      */
writeObjects(@onNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, @NonNull String fieldName, long fieldId, @NonNull Object[] objects)127     static void writeObjects(@NonNull DualDumpOutputStream dumpOutputStream,
128             boolean dumpAsProto, @NonNull String fieldName, long fieldId,
129             @NonNull Object[] objects) {
130         if (!dumpAsProto) {
131             dumpOutputStream.write(fieldName, fieldId, Arrays.toString(objects));
132             return;
133         }
134         for (Object object : objects) {
135             writeObject(dumpOutputStream, fieldName, fieldId, object);
136         }
137     }
138 
139     /**
140      * Writes the given integers to a dump in text or proto format. In the latter case, the field
141      * must be {@code repeated}.
142      */
writeInts(@onNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, @NonNull String fieldName, long fieldId, @NonNull int[] vals)143     static void writeInts(@NonNull DualDumpOutputStream dumpOutputStream,
144             boolean dumpAsProto, @NonNull String fieldName, long fieldId, @NonNull int[] vals) {
145         if (!dumpAsProto) {
146             dumpOutputStream.write(fieldName, fieldId, Arrays.toString(vals));
147             return;
148         }
149         for (int val : vals) {
150             dumpOutputStream.write(fieldName, fieldId, val);
151         }
152     }
153 
154     /**
155      * Writes the given keycodes to a dump in text or proto format. In the former case, the keycodes
156      * are written as {@link KeyCode} constants. In the latter case, the field must be {@code
157      * repeated}.
158      */
writeKeyCodes(@onNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, @NonNull String fieldName, long fieldId, @NonNull int[] vals)159     static void writeKeyCodes(@NonNull DualDumpOutputStream dumpOutputStream,
160             boolean dumpAsProto, @NonNull String fieldName, long fieldId, @NonNull int[] vals) {
161         if (!dumpAsProto) {
162             StringBuilder sb = new StringBuilder();
163             sb.append('[');
164             for (int i = 0; i < vals.length; i++) {
165                 if (i > 0) {
166                     sb.append(", ");
167                 }
168                 sb.append(KeyEvent.keyCodeToString(vals[i]));
169             }
170             sb.append(']');
171             dumpOutputStream.write(fieldName, fieldId, sb.toString());
172             return;
173         }
174         for (int val : vals) {
175             dumpOutputStream.write(fieldName, fieldId, val);
176         }
177     }
178 
179     /**
180      * Writes the given CharSequences to a dump in text or proto format, converting them to strings.
181      * In the latter case, the field must be {@code repeated}.
182      */
writeCharSequences(@onNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, @NonNull String fieldName, long fieldId, @NonNull Collection<CharSequence> vals)183     static void writeCharSequences(@NonNull DualDumpOutputStream dumpOutputStream,
184             boolean dumpAsProto, @NonNull String fieldName, long fieldId,
185             @NonNull Collection<CharSequence> vals) {
186         if (!dumpAsProto) {
187             dumpOutputStream.write(fieldName, fieldId, vals.toString());
188             return;
189         }
190         for (CharSequence val : vals) {
191             dumpOutputStream.write(fieldName, fieldId, val.toString());
192         }
193     }
194 
195     /**
196      * Writes the given integers to a dump in text or proto format. In the latter case, the field
197      * must be {@code repeated}.
198      */
writeIntegers(@onNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, @NonNull String fieldName, long fieldId, @NonNull Collection<Integer> vals)199     static void writeIntegers(@NonNull DualDumpOutputStream dumpOutputStream,
200             boolean dumpAsProto, @NonNull String fieldName, long fieldId,
201             @NonNull Collection<Integer> vals) {
202         if (!dumpAsProto) {
203             dumpOutputStream.write(fieldName, fieldId, vals.toString());
204             return;
205         }
206         for (Integer val : vals) {
207             dumpOutputStream.write(fieldName, fieldId, val);
208         }
209     }
210 
211     /**
212      * Writes the given map from window ID to window type to a dump in text or proto format. In the
213      * former case, the window types are written as {@link AccessibilityWindowInfo} constants. In
214      * the latter case, the field must be a {@code map}.
215      */
writeWindowTypes(@onNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, @NonNull String fieldName, long fieldId, @NonNull Map<Integer, Integer> map)216     static void writeWindowTypes(@NonNull DualDumpOutputStream dumpOutputStream,
217             boolean dumpAsProto, @NonNull String fieldName, long fieldId,
218             @NonNull Map<Integer, Integer> map) {
219         if (!dumpAsProto) {
220             long fieldToken = dumpOutputStream.start(fieldName, fieldId);
221             for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
222                 dumpOutputStream.write(/* fieldName= */ entry.getKey().toString(), /* fieldId= */ 0,
223                         AccessibilityWindowInfo.typeToString(entry.getValue()));
224             }
225             dumpOutputStream.end(fieldToken);
226             return;
227         }
228         for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
229             long fieldToken = dumpOutputStream.start(fieldName, fieldId);
230             dumpOutputStream.write("key", /* fieldId= */ 1, entry.getKey());
231             dumpOutputStream.write("value", /* fieldId= */ 2, entry.getValue());
232             dumpOutputStream.end(fieldToken);
233         }
234     }
235 
236     /**
237      * Writes the given map from window ID to node to a dump in text or proto format. In both cases,
238      * the nodes are written as {@code toString}s. In the latter case, the field must be a {@code
239      * map}.
240      */
writeFocusedNodes(@onNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, @NonNull String fieldName, long fieldId, @NonNull Map<Integer, AccessibilityNodeInfo> map)241     static void writeFocusedNodes(@NonNull DualDumpOutputStream dumpOutputStream,
242             boolean dumpAsProto, @NonNull String fieldName, long fieldId,
243             @NonNull Map<Integer, AccessibilityNodeInfo> map) {
244         if (!dumpAsProto) {
245             long fieldToken = dumpOutputStream.start(fieldName, fieldId);
246             for (Map.Entry<Integer, AccessibilityNodeInfo> entry : map.entrySet()) {
247                 dumpOutputStream.write(/* fieldName= */ entry.getKey().toString(), /* fieldId= */ 0,
248                         entry.getValue().toString());
249             }
250             dumpOutputStream.end(fieldToken);
251             return;
252         }
253         for (Map.Entry<Integer, AccessibilityNodeInfo> entry : map.entrySet()) {
254             long fieldToken = dumpOutputStream.start(fieldName, fieldId);
255             dumpOutputStream.write("key", /* fieldId= */ 1, entry.getKey());
256             dumpOutputStream.write("value", /* fieldId= */ 2, entry.getValue().toString());
257             dumpOutputStream.end(fieldToken);
258         }
259     }
260 }
261