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