1 /*
2  * Copyright (C) 2016 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 android.server.cts;
18 
19 import com.android.ddmlib.IShellOutputReceiver;
20 import com.android.tradefed.device.DeviceNotAvailableException;
21 import com.android.tradefed.device.ITestDevice;
22 import com.android.tradefed.log.LogUtil.CLog;
23 
24 import java.awt.Rectangle;
25 import java.io.ByteArrayInputStream;
26 import java.io.DataInputStream;
27 import java.io.IOException;
28 import java.lang.System;
29 import junit.framework.Assert;
30 
31 import static android.server.cts.StateLogger.logE;
32 
33 // Parses a trace of surface commands from the WM (in real time)
34 // and dispenses them via the SurfaceObserver interface.
35 //
36 // Data enters through addOutput
37 public class SurfaceTraceReceiver implements IShellOutputReceiver {
38     final SurfaceObserver mObserver;
39 
40     private State mState = State.CMD;
41     private String mCurrentWindowName = null;
42     private int mArgPosition = 0;
43     private float[] mTmpFloats = new float[10];
44     private int[] mTmpInts = new int[10];
45     private Rectangle.Float mTmpRect = new Rectangle.Float();
46     private byte[] mUnprocessedBytes = new byte[16384];
47     private byte[] mFullData = new byte[32768];
48     private int mUnprocessedBytesLength;
49 
50     private boolean mCancelled = false;
51 
52     interface SurfaceObserver {
setAlpha(String windowName, float alpha)53         default void setAlpha(String windowName, float alpha) {}
setLayer(String windowName, int layer)54         default void setLayer(String windowName, int layer) {}
setPosition(String windowName, float x, float y)55         default void setPosition(String windowName, float x, float y) {}
setSize(String widnowName, int width, int height)56         default void setSize(String widnowName, int width, int height) {}
setLayerStack(String windowName, int layerStack)57         default void setLayerStack(String windowName, int layerStack) {}
setMatrix(String windowName, float dsdx, float dtdx, float dsdy, float dtdy)58         default void setMatrix(String windowName, float dsdx, float dtdx, float dsdy, float dtdy) {}
setCrop(String windowName, Rectangle.Float crop)59         default void setCrop(String windowName, Rectangle.Float crop) {}
setFinalCrop(String windowName, Rectangle.Float finalCrop)60         default void setFinalCrop(String windowName, Rectangle.Float finalCrop) {}
hide(String windowName)61         default void hide(String windowName) {}
show(String windowName)62         default void show(String windowName) {}
setGeometryAppliesWithResize(String windowName)63         default void setGeometryAppliesWithResize(String windowName) {}
openTransaction()64         default void openTransaction() {}
closeTransaction()65         default void closeTransaction() {}
66     };
67 
68     enum State {
69         CMD,
70         SET_ALPHA,
71         SET_LAYER,
72         SET_POSITION,
73         SET_SIZE,
74         SET_CROP,
75         SET_FINAL_CROP,
76         SET_LAYER_STACK,
77         SET_MATRIX,
78         HIDE,
79         SHOW,
80         GEOMETRY_APPLIES_WITH_RESIZE
81     };
82 
SurfaceTraceReceiver(SurfaceObserver observer)83     SurfaceTraceReceiver(SurfaceObserver observer) {
84         mObserver = observer;
85     }
86 
87     // Reset state and prepare to accept a new command.
nextCmd(DataInputStream d)88     void nextCmd(DataInputStream d) {
89         mState = State.CMD;
90         mCurrentWindowName = null;
91         mArgPosition = 0;
92 
93         try {
94             // Consume the sigil
95             d.readByte();
96             d.readByte();
97             d.readByte();
98             d.readByte();
99         } catch (Exception e) {
100             logE("Exception consuming sigil: " + e);
101         }
102     }
103 
104     // When the command parsing functions below are called, the window name
105     // will already be parsed. The responsibility of these functions
106     // is to parse other arguments 1 by 1 and accumlate them until the appropriate number
107     // is reached. At that point the parser should emit an event to the observer and
108     // call nextCmd
parseAlpha(DataInputStream d)109     void parseAlpha(DataInputStream d) throws IOException {
110         float alpha = d.readFloat();
111         mObserver.setAlpha(mCurrentWindowName, alpha);
112         nextCmd(d);
113     }
114 
parseLayer(DataInputStream d)115     void parseLayer(DataInputStream d) throws IOException {
116         int layer = d.readInt();
117         mObserver.setLayer(mCurrentWindowName, layer);
118         nextCmd(d);
119     }
120 
parsePosition(DataInputStream d)121     void parsePosition(DataInputStream d) throws IOException {
122         mTmpFloats[mArgPosition] = d.readFloat();
123         mArgPosition++;
124         if (mArgPosition == 2)  {
125             mObserver.setPosition(mCurrentWindowName, mTmpFloats[0], mTmpFloats[1]);
126             nextCmd(d);
127         }
128     }
129 
parseSize(DataInputStream d)130     void parseSize(DataInputStream d) throws IOException {
131         mTmpInts[mArgPosition] = d.readInt();
132         mArgPosition++;
133         if (mArgPosition == 2) {
134             mObserver.setSize(mCurrentWindowName, mTmpInts[0], mTmpInts[1]);
135             nextCmd(d);
136         }
137     }
138 
139     // Careful Android rectangle rep is top-left-right-bottom awt is top-left-width-height
parseCrop(DataInputStream d)140     void parseCrop(DataInputStream d) throws IOException {
141         mTmpFloats[mArgPosition] = d.readFloat();
142         mArgPosition++;
143         if (mArgPosition == 4) {
144             mTmpRect.setRect(mTmpFloats[0], mTmpFloats[1], mTmpFloats[2]-mTmpFloats[0],
145                     mTmpFloats[3]-mTmpFloats[1]);
146             mObserver.setCrop(mCurrentWindowName, mTmpRect);
147             nextCmd(d);
148         }
149     }
150 
parseFinalCrop(DataInputStream d)151     void parseFinalCrop(DataInputStream d) throws IOException {
152         mTmpFloats[mArgPosition] = d.readInt();
153         mArgPosition++;
154         if (mArgPosition == 4) {
155             mTmpRect.setRect(mTmpFloats[0], mTmpFloats[1], mTmpFloats[2]-mTmpFloats[0],
156                     mTmpFloats[3]-mTmpFloats[1]);
157             mObserver.setFinalCrop(mCurrentWindowName, mTmpRect);
158             nextCmd(d);
159         }
160     }
161 
parseLayerStack(DataInputStream d)162     void parseLayerStack(DataInputStream d) throws IOException {
163         int layerStack = d.readInt();
164         mObserver.setLayerStack(mCurrentWindowName, layerStack);
165         nextCmd(d);
166     }
167 
parseSetMatrix(DataInputStream d)168     void parseSetMatrix(DataInputStream d) throws IOException {
169         mTmpFloats[mArgPosition] = d.readFloat();
170         mArgPosition++;
171         if (mArgPosition == 4) {
172             mObserver.setMatrix(mCurrentWindowName, mTmpFloats[0],
173                     mTmpFloats[1], mTmpFloats[2], mTmpFloats[3]);
174             nextCmd(d);
175         }
176     }
177 
parseHide(DataInputStream d)178     void parseHide(DataInputStream d) throws IOException {
179         mObserver.hide(mCurrentWindowName);
180         nextCmd(d);
181     }
182 
parseShow(DataInputStream d)183     void parseShow(DataInputStream d) throws IOException {
184         mObserver.show(mCurrentWindowName);
185         nextCmd(d);
186     }
187 
parseGeometryAppliesWithResize(DataInputStream d)188     void parseGeometryAppliesWithResize(DataInputStream d) throws IOException {
189         mObserver.setGeometryAppliesWithResize(mCurrentWindowName);
190         nextCmd(d);
191     }
192 
indexAfterLastSigil(byte[] data, int offset, int length)193     public int indexAfterLastSigil(byte[] data, int offset, int length) {
194         int idx = offset + length - 1;
195         int sigilsNeeded = 4;
196         byte sigil = (byte)0xfc;
197         while (idx > offset) {
198             if (data[idx] == sigil) {
199                 sigilsNeeded--;
200                 if (sigilsNeeded == 0) {
201                     return idx+4;
202                 }
203             } else {
204                 sigilsNeeded = 4;
205             }
206             idx--;
207         }
208         return idx; // idx == offset at this point
209     }
210 
211     // The tricky bit here is ADB may break up our words, and not send us complete messages,
212     // or even complete integers! To ensure we process the data in appropciate chunks,
213     // We look for a sigil (0xfcfcfcfc) and only process data when it ends in as igil.
214     // Otherwise we save it and wait to receive a sigil, then process the merged data.
addOutput(byte[] data, int offset, int length)215     public void addOutput(byte[] data, int offset, int length) {
216         byte[] combinedData = data;
217 
218         // First we have to merge any unprocessed bytes from the last call in to
219         // a combined array.
220         if (mUnprocessedBytesLength > 0) {
221             System.arraycopy(mUnprocessedBytes, 0, mFullData, 0, mUnprocessedBytesLength);
222             System.arraycopy(data, offset, mFullData, mUnprocessedBytesLength, length);
223             combinedData = mFullData;
224             length = mUnprocessedBytesLength + length;
225             offset = 0;
226             mUnprocessedBytesLength = 0;
227         }
228 
229         // Now we find the last sigil in our combined array. Everything before this index is
230         // a properly terminated message ready to be parsed.
231         int completedIndex = indexAfterLastSigil(combinedData, offset, length);
232         // If there are any bytes left after the last sigil, save them for next time.
233         if (completedIndex != length + offset) {
234             mUnprocessedBytesLength = (length+offset)-(completedIndex);
235             System.arraycopy(combinedData, completedIndex,
236                     mUnprocessedBytes, 0, mUnprocessedBytesLength);
237         }
238         //  If there was no sigil, we have nothing to process yet.
239         if (completedIndex <= offset) {
240             return;
241         }
242         ByteArrayInputStream b = new ByteArrayInputStream(combinedData, offset, completedIndex - offset);
243         DataInputStream d = new DataInputStream(b);
244 
245         // We may not receive an entire message at once (for example we may receive
246         // a command without its arguments), so we track our current state, over multiple
247         // addOutput calls. When we are in State.CMD it means we next expect a new command.
248         // If we are not expecting a command, then all commands with arguments, begin with
249         // a window name. Once we have the window name, individual parseAlpha,
250         // parseLayer, etc...statements will parse command arguments one at a time. Once
251         // the appropriate number of arguments is collected the observer will be invoked
252         // and the state reset. For commands which have no arguments (e.g. open/close transaction),
253         // parseCmd can emit the observer event and call nextCmd() right away.
254         try {
255             while (b.available() > 0) {
256                 if (mState != State.CMD && mCurrentWindowName == null) {
257                     mCurrentWindowName = d.readUTF();
258                     if (b.available() == 0) {
259                         return;
260                     }
261                 }
262                 switch (mState) {
263                 case CMD: {
264                     String cmd = d.readUTF();
265                     parseCmd(d, cmd);
266                     break;
267                 }
268                 case SET_ALPHA: {
269                     parseAlpha(d);
270                     break;
271                 }
272                 case SET_LAYER: {
273                     parseLayer(d);
274                     break;
275                 }
276                 case SET_POSITION: {
277                     parsePosition(d);
278                     break;
279                 }
280                 case SET_SIZE: {
281                     parseSize(d);
282                     break;
283                 }
284                 case SET_CROP: {
285                     parseCrop(d);
286                     break;
287                 }
288                 case SET_FINAL_CROP: {
289                     parseFinalCrop(d);
290                     break;
291                 }
292                 case SET_LAYER_STACK: {
293                     parseLayerStack(d);
294                     break;
295                 }
296                 case SET_MATRIX: {
297                     parseSetMatrix(d);
298                     break;
299                 }
300                 case HIDE: {
301                     parseHide(d);
302                     break;
303                 }
304                 case SHOW: {
305                     parseShow(d);
306                     break;
307                 }
308                 case GEOMETRY_APPLIES_WITH_RESIZE: {
309                     parseGeometryAppliesWithResize(d);
310                     break;
311                 }
312                 }
313             }
314         } catch (Exception e) {
315             logE("Error in surface trace receiver: " + e.toString());
316         }
317     }
318 
parseCmd(DataInputStream d, String cmd)319     void parseCmd(DataInputStream d, String cmd) {
320         switch (cmd) {
321         case "Alpha":
322             mState = State.SET_ALPHA;
323             break;
324         case "Layer":
325             mState = State.SET_LAYER;
326             break;
327         case "Position":
328             mState = State.SET_POSITION;
329             break;
330         case "Size":
331             mState = State.SET_SIZE;
332             break;
333         case "Crop":
334             mState = State.SET_CROP;
335             break;
336         case "FinalCrop":
337             mState = State.SET_FINAL_CROP;
338             break;
339         case "LayerStack":
340             mState = State.SET_LAYER_STACK;
341             break;
342         case "Matrix":
343             mState = State.SET_MATRIX;
344             break;
345         case "Hide":
346             mState = State.HIDE;
347             break;
348         case "Show":
349             mState = State.SHOW;
350             break;
351         case "GeometryAppliesWithResize":
352             mState = State.GEOMETRY_APPLIES_WITH_RESIZE;
353             break;
354         case "OpenTransaction":
355             mObserver.openTransaction();
356             nextCmd(d);
357             break;
358         case "CloseTransaction":
359             mObserver.closeTransaction();
360             nextCmd(d);
361             break;
362         default:
363             Assert.fail("Unexpected surface command: " + cmd);
364             break;
365         }
366     }
367 
368     @Override
flush()369     public void flush() {
370     }
371 
cancel()372     void cancel() {
373         mCancelled = true;
374     }
375 
376     @Override
isCancelled()377     public boolean isCancelled() {
378         return mCancelled;
379     }
380 }
381