1 /* 2 * Copyright (C) 2013 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.ddm; 18 19 import static com.android.internal.util.Preconditions.checkArgument; 20 21 import android.util.Log; 22 import android.view.View; 23 import android.view.ViewDebug; 24 import android.view.ViewRootImpl; 25 import android.view.WindowManagerGlobal; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import org.apache.harmony.dalvik.ddmc.Chunk; 30 import org.apache.harmony.dalvik.ddmc.ChunkHandler; 31 import org.apache.harmony.dalvik.ddmc.DdmServer; 32 33 import java.io.BufferedWriter; 34 import java.io.ByteArrayOutputStream; 35 import java.io.DataOutputStream; 36 import java.io.IOException; 37 import java.io.OutputStreamWriter; 38 import java.lang.reflect.Method; 39 import java.nio.BufferUnderflowException; 40 import java.nio.ByteBuffer; 41 import java.nio.charset.StandardCharsets; 42 43 /** 44 * Handle various requests related to profiling / debugging of the view system. 45 * Support for these features are advertised via {@link DdmHandleHello}. 46 */ 47 public class DdmHandleViewDebug extends DdmHandle { 48 /** List {@link ViewRootImpl}'s of this process. */ 49 private static final int CHUNK_VULW = ChunkHandler.type("VULW"); 50 51 /** Operation on view root, first parameter in packet should be one of VURT_* constants */ 52 private static final int CHUNK_VURT = ChunkHandler.type("VURT"); 53 54 /** Dump view hierarchy. */ 55 private static final int VURT_DUMP_HIERARCHY = 1; 56 57 /** Capture View Layers. */ 58 private static final int VURT_CAPTURE_LAYERS = 2; 59 60 /** Dump View Theme. */ 61 private static final int VURT_DUMP_THEME = 3; 62 63 /** 64 * Generic View Operation, first parameter in the packet should be one of the 65 * VUOP_* constants below. 66 */ 67 private static final int CHUNK_VUOP = ChunkHandler.type("VUOP"); 68 69 /** Capture View. */ 70 private static final int VUOP_CAPTURE_VIEW = 1; 71 72 /** Obtain the Display List corresponding to the view. */ 73 private static final int VUOP_DUMP_DISPLAYLIST = 2; 74 75 /** Profile a view. */ 76 private static final int VUOP_PROFILE_VIEW = 3; 77 78 /** Invoke a method on the view. */ 79 private static final int VUOP_INVOKE_VIEW_METHOD = 4; 80 81 /** Set layout parameter. */ 82 private static final int VUOP_SET_LAYOUT_PARAMETER = 5; 83 84 /** Error code indicating operation specified in chunk is invalid. */ 85 private static final int ERR_INVALID_OP = -1; 86 87 /** Error code indicating that the parameters are invalid. */ 88 private static final int ERR_INVALID_PARAM = -2; 89 90 /** Error code indicating an exception while performing operation. */ 91 private static final int ERR_EXCEPTION = -3; 92 93 private static final String TAG = "DdmViewDebug"; 94 95 private static final DdmHandleViewDebug sInstance = new DdmHandleViewDebug(); 96 97 /** singleton, do not instantiate. */ DdmHandleViewDebug()98 private DdmHandleViewDebug() {} 99 register()100 public static void register() { 101 DdmServer.registerHandler(CHUNK_VULW, sInstance); 102 DdmServer.registerHandler(CHUNK_VURT, sInstance); 103 DdmServer.registerHandler(CHUNK_VUOP, sInstance); 104 } 105 106 @Override onConnected()107 public void onConnected() { 108 } 109 110 @Override onDisconnected()111 public void onDisconnected() { 112 } 113 114 @Override handleChunk(Chunk request)115 public Chunk handleChunk(Chunk request) { 116 int type = request.type; 117 118 if (type == CHUNK_VULW) { 119 return listWindows(); 120 } 121 122 ByteBuffer in = wrapChunk(request); 123 int op = in.getInt(); 124 125 View rootView = getRootView(in); 126 if (rootView == null) { 127 return createFailChunk(ERR_INVALID_PARAM, "Invalid View Root"); 128 } 129 130 if (type == CHUNK_VURT) { 131 if (op == VURT_DUMP_HIERARCHY) { 132 return dumpHierarchy(rootView, in); 133 } else if (op == VURT_CAPTURE_LAYERS) { 134 return captureLayers(rootView); 135 } else if (op == VURT_DUMP_THEME) { 136 return dumpTheme(rootView); 137 } else { 138 return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op); 139 } 140 } 141 142 final View targetView = getTargetView(rootView, in); 143 if (targetView == null) { 144 return createFailChunk(ERR_INVALID_PARAM, "Invalid target view"); 145 } 146 147 if (type == CHUNK_VUOP) { 148 switch (op) { 149 case VUOP_CAPTURE_VIEW: 150 return captureView(rootView, targetView); 151 case VUOP_DUMP_DISPLAYLIST: 152 return dumpDisplayLists(rootView, targetView); 153 case VUOP_PROFILE_VIEW: 154 return profileView(rootView, targetView); 155 case VUOP_INVOKE_VIEW_METHOD: 156 return invokeViewMethod(rootView, targetView, in); 157 case VUOP_SET_LAYOUT_PARAMETER: 158 return setLayoutParameter(rootView, targetView, in); 159 default: 160 return createFailChunk(ERR_INVALID_OP, "Unknown view operation: " + op); 161 } 162 } else { 163 throw new RuntimeException("Unknown packet " + name(type)); 164 } 165 } 166 167 /** Returns the list of windows owned by this client. */ listWindows()168 private Chunk listWindows() { 169 String[] windowNames = WindowManagerGlobal.getInstance().getViewRootNames(); 170 171 int responseLength = 4; // # of windows 172 for (String name : windowNames) { 173 responseLength += 4; // length of next window name 174 responseLength += name.length() * 2; // window name 175 } 176 177 ByteBuffer out = ByteBuffer.allocate(responseLength); 178 out.order(ChunkHandler.CHUNK_ORDER); 179 180 out.putInt(windowNames.length); 181 for (String name : windowNames) { 182 out.putInt(name.length()); 183 putString(out, name); 184 } 185 186 return new Chunk(CHUNK_VULW, out); 187 } 188 getRootView(ByteBuffer in)189 private View getRootView(ByteBuffer in) { 190 try { 191 int viewRootNameLength = in.getInt(); 192 String viewRootName = getString(in, viewRootNameLength); 193 return WindowManagerGlobal.getInstance().getRootView(viewRootName); 194 } catch (BufferUnderflowException e) { 195 return null; 196 } 197 } 198 getTargetView(View root, ByteBuffer in)199 private View getTargetView(View root, ByteBuffer in) { 200 int viewLength; 201 String viewName; 202 203 try { 204 viewLength = in.getInt(); 205 viewName = getString(in, viewLength); 206 } catch (BufferUnderflowException e) { 207 return null; 208 } 209 210 return ViewDebug.findView(root, viewName); 211 } 212 213 /** 214 * Returns the view hierarchy and/or view properties starting at the provided view. 215 * Based on the input options, the return data may include: 216 * - just the view hierarchy 217 * - view hierarchy & the properties for each of the views 218 * - just the view properties for a specific view. 219 * TODO: Currently this only returns views starting at the root, need to fix so that 220 * it can return properties of any view. 221 */ dumpHierarchy(View rootView, ByteBuffer in)222 private Chunk dumpHierarchy(View rootView, ByteBuffer in) { 223 boolean skipChildren = in.getInt() > 0; 224 boolean includeProperties = in.getInt() > 0; 225 boolean v2 = in.hasRemaining() && in.getInt() > 0; 226 227 long start = System.currentTimeMillis(); 228 229 ByteArrayOutputStream b = new ByteArrayOutputStream(2 * 1024 * 1024); 230 try { 231 if (v2) { 232 ViewDebug.dumpv2(rootView, b); 233 } else { 234 ViewDebug.dump(rootView, skipChildren, includeProperties, b); 235 } 236 } catch (IOException | InterruptedException e) { 237 return createFailChunk(1, "Unexpected error while obtaining view hierarchy: " 238 + e.getMessage()); 239 } 240 241 long end = System.currentTimeMillis(); 242 Log.d(TAG, "Time to obtain view hierarchy (ms): " + (end - start)); 243 244 byte[] data = b.toByteArray(); 245 return new Chunk(CHUNK_VURT, data, 0, data.length); 246 } 247 248 /** Returns a buffer with region details & bitmap of every single view. */ captureLayers(View rootView)249 private Chunk captureLayers(View rootView) { 250 ByteArrayOutputStream b = new ByteArrayOutputStream(1024); 251 DataOutputStream dos = new DataOutputStream(b); 252 try { 253 ViewDebug.captureLayers(rootView, dos); 254 } catch (IOException e) { 255 return createFailChunk(1, "Unexpected error while obtaining view hierarchy: " 256 + e.getMessage()); 257 } finally { 258 try { 259 dos.close(); 260 } catch (IOException e) { 261 // ignore 262 } 263 } 264 265 byte[] data = b.toByteArray(); 266 return new Chunk(CHUNK_VURT, data, 0, data.length); 267 } 268 269 /** 270 * Returns the Theme dump of the provided view. 271 */ dumpTheme(View rootView)272 private Chunk dumpTheme(View rootView) { 273 ByteArrayOutputStream b = new ByteArrayOutputStream(1024); 274 try { 275 ViewDebug.dumpTheme(rootView, b); 276 } catch (IOException e) { 277 return createFailChunk(1, "Unexpected error while dumping the theme: " 278 + e.getMessage()); 279 } 280 281 byte[] data = b.toByteArray(); 282 return new Chunk(CHUNK_VURT, data, 0, data.length); 283 } 284 captureView(View rootView, View targetView)285 private Chunk captureView(View rootView, View targetView) { 286 ByteArrayOutputStream b = new ByteArrayOutputStream(1024); 287 try { 288 ViewDebug.capture(rootView, b, targetView); 289 } catch (IOException e) { 290 return createFailChunk(1, "Unexpected error while capturing view: " 291 + e.getMessage()); 292 } 293 294 byte[] data = b.toByteArray(); 295 return new Chunk(CHUNK_VUOP, data, 0, data.length); 296 } 297 298 /** Returns the display lists corresponding to the provided view. */ dumpDisplayLists(final View rootView, final View targetView)299 private Chunk dumpDisplayLists(final View rootView, final View targetView) { 300 rootView.post(new Runnable() { 301 @Override 302 public void run() { 303 ViewDebug.outputDisplayList(rootView, targetView); 304 } 305 }); 306 return null; 307 } 308 309 /** 310 * Invokes provided method on the view. 311 * The method name and its arguments are passed in as inputs via the byte buffer. 312 * The buffer contains:<ol> 313 * <li> len(method name) </li> 314 * <li> method name (encoded as UTF-16 2-byte characters) </li> 315 * <li> # of args </li> 316 * <li> arguments: Each argument comprises of a type specifier followed by the actual argument. 317 * The type specifier is one character modelled after JNI signatures: 318 * <ul> 319 * <li>[ - array<br> 320 * This is followed by a second character according to this spec, indicating the 321 * array type, then the array length as an Int, followed by a repeated encoding 322 * of the actual data. 323 * WARNING: Only <b>byte[]</b> is supported currently. 324 * </li> 325 * <li>Z - boolean<br> 326 * Booleans are encoded via bytes with 0 indicating false</li> 327 * <li>B - byte</li> 328 * <li>C - char</li> 329 * <li>S - short</li> 330 * <li>I - int</li> 331 * <li>J - long</li> 332 * <li>F - float</li> 333 * <li>D - double</li> 334 * <li>V - void<br> 335 * NOT followed by a value. Only used for return types</li> 336 * <li>R - String (not a real JNI type, but added for convenience)<br> 337 * Strings are encoded as an unsigned short of the number of <b>bytes</b>, 338 * followed by the actual UTF-8 encoded bytes. 339 * WARNING: This is the same encoding as produced by 340 * ViewHierarchyEncoder#writeString. However, note that this encoding is 341 * different to what DdmHandle#getString() expects, which is used in other places 342 * in this class. 343 * WARNING: Since the length is the number of UTF-8 encoded bytes, Strings can 344 * contain up to 64k ASCII characters, yet depending on the actual data, the true 345 * maximum might be as little as 21844 unicode characters. 346 * <b>null</b> String objects are encoded as an empty string 347 * </li> 348 * </ul> 349 * </li> 350 * </ol> 351 * Methods that take no arguments need only specify the method name. 352 * 353 * The return value is encoded the same way as a single parameter (type + value) 354 */ invokeViewMethod(final View rootView, final View targetView, ByteBuffer in)355 private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) { 356 int l = in.getInt(); 357 String methodName = getString(in, l); 358 359 Class<?>[] argTypes; 360 Object[] args; 361 if (!in.hasRemaining()) { 362 argTypes = new Class<?>[0]; 363 args = new Object[0]; 364 } else { 365 int nArgs = in.getInt(); 366 argTypes = new Class<?>[nArgs]; 367 args = new Object[nArgs]; 368 369 try { 370 deserializeMethodParameters(args, argTypes, in); 371 } catch (ViewMethodInvocationSerializationException e) { 372 return createFailChunk(ERR_INVALID_PARAM, e.getMessage()); 373 } 374 } 375 376 Method method; 377 try { 378 method = targetView.getClass().getMethod(methodName, argTypes); 379 } catch (NoSuchMethodException e) { 380 Log.e(TAG, "No such method: " + e.getMessage()); 381 return createFailChunk(ERR_INVALID_PARAM, 382 "No such method: " + e.getMessage()); 383 } 384 385 try { 386 Object result = ViewDebug.invokeViewMethod(targetView, method, args); 387 Class<?> returnType = method.getReturnType(); 388 byte[] returnValue = serializeReturnValue(returnType, returnType.cast(result)); 389 return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length); 390 } catch (Exception e) { 391 Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage()); 392 String msg = e.getCause().getMessage(); 393 if (msg == null) { 394 msg = e.getCause().toString(); 395 } 396 return createFailChunk(ERR_EXCEPTION, msg); 397 } 398 } 399 setLayoutParameter(final View rootView, final View targetView, ByteBuffer in)400 private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) { 401 int l = in.getInt(); 402 String param = getString(in, l); 403 int value = in.getInt(); 404 try { 405 ViewDebug.setLayoutParameter(targetView, param, value); 406 } catch (Exception e) { 407 Log.e(TAG, "Exception setting layout parameter: " + e); 408 return createFailChunk(ERR_EXCEPTION, "Error accessing field " 409 + param + ":" + e.getMessage()); 410 } 411 412 return null; 413 } 414 415 /** Profiles provided view. */ profileView(View rootView, final View targetView)416 private Chunk profileView(View rootView, final View targetView) { 417 ByteArrayOutputStream b = new ByteArrayOutputStream(32 * 1024); 418 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(b), 32 * 1024); 419 try { 420 ViewDebug.profileViewAndChildren(targetView, bw); 421 } catch (IOException e) { 422 return createFailChunk(1, "Unexpected error while profiling view: " + e.getMessage()); 423 } finally { 424 try { 425 bw.close(); 426 } catch (IOException e) { 427 // ignore 428 } 429 } 430 431 byte[] data = b.toByteArray(); 432 return new Chunk(CHUNK_VUOP, data, 0, data.length); 433 } 434 435 /** 436 * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in} 437 * buffer. 438 * 439 * The length of {@code args} determines how many arguments are read. The {@code argTypes} must 440 * be the same length, and will be set to the argument types of the data read. 441 * 442 * @hide 443 */ 444 @VisibleForTesting deserializeMethodParameters( Object[] args, Class<?>[] argTypes, ByteBuffer in)445 public static void deserializeMethodParameters( 446 Object[] args, Class<?>[] argTypes, ByteBuffer in) throws 447 ViewMethodInvocationSerializationException { 448 checkArgument(args.length == argTypes.length); 449 450 for (int i = 0; i < args.length; i++) { 451 char typeSignature = in.getChar(); 452 boolean isArray = typeSignature == SIG_ARRAY; 453 if (isArray) { 454 char arrayType = in.getChar(); 455 if (arrayType != SIG_BYTE) { 456 // This implementation only supports byte-arrays for now. 457 throw new ViewMethodInvocationSerializationException( 458 "Unsupported array parameter type (" + typeSignature 459 + ") to invoke view method @argument " + i); 460 } 461 462 int arrayLength = in.getInt(); 463 if (arrayLength > in.remaining()) { 464 // The sender did not actually sent the specified amount of bytes. This 465 // avoids a malformed packet to trigger an out-of-memory error. 466 throw new BufferUnderflowException(); 467 } 468 469 byte[] byteArray = new byte[arrayLength]; 470 in.get(byteArray); 471 472 argTypes[i] = byte[].class; 473 args[i] = byteArray; 474 } else { 475 switch (typeSignature) { 476 case SIG_BOOLEAN: 477 argTypes[i] = boolean.class; 478 args[i] = in.get() != 0; 479 break; 480 case SIG_BYTE: 481 argTypes[i] = byte.class; 482 args[i] = in.get(); 483 break; 484 case SIG_CHAR: 485 argTypes[i] = char.class; 486 args[i] = in.getChar(); 487 break; 488 case SIG_SHORT: 489 argTypes[i] = short.class; 490 args[i] = in.getShort(); 491 break; 492 case SIG_INT: 493 argTypes[i] = int.class; 494 args[i] = in.getInt(); 495 break; 496 case SIG_LONG: 497 argTypes[i] = long.class; 498 args[i] = in.getLong(); 499 break; 500 case SIG_FLOAT: 501 argTypes[i] = float.class; 502 args[i] = in.getFloat(); 503 break; 504 case SIG_DOUBLE: 505 argTypes[i] = double.class; 506 args[i] = in.getDouble(); 507 break; 508 case SIG_STRING: { 509 argTypes[i] = String.class; 510 int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort()); 511 byte[] rawStringBuffer = new byte[stringUtf8ByteCount]; 512 in.get(rawStringBuffer); 513 args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8); 514 break; 515 } 516 default: 517 Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature); 518 throw new ViewMethodInvocationSerializationException( 519 "Unsupported parameter type (" + typeSignature 520 + ") to invoke view method."); 521 } 522 } 523 524 } 525 } 526 527 /** 528 * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD. 529 * @hide 530 */ 531 @VisibleForTesting serializeReturnValue(Class<?> type, Object value)532 public static byte[] serializeReturnValue(Class<?> type, Object value) 533 throws ViewMethodInvocationSerializationException, IOException { 534 ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024); 535 DataOutputStream dos = new DataOutputStream(byteOutStream); 536 537 if (type.isArray()) { 538 if (!type.equals(byte[].class)) { 539 // Only byte arrays are supported currently. 540 throw new ViewMethodInvocationSerializationException( 541 "Unsupported array return type (" + type + ")"); 542 } 543 byte[] byteArray = (byte[]) value; 544 dos.writeChar(SIG_ARRAY); 545 dos.writeChar(SIG_BYTE); 546 dos.writeInt(byteArray.length); 547 dos.write(byteArray); 548 } else if (boolean.class.equals(type)) { 549 dos.writeChar(SIG_BOOLEAN); 550 dos.write((boolean) value ? 1 : 0); 551 } else if (byte.class.equals(type)) { 552 dos.writeChar(SIG_BYTE); 553 dos.writeByte((byte) value); 554 } else if (char.class.equals(type)) { 555 dos.writeChar(SIG_CHAR); 556 dos.writeChar((char) value); 557 } else if (short.class.equals(type)) { 558 dos.writeChar(SIG_SHORT); 559 dos.writeShort((short) value); 560 } else if (int.class.equals(type)) { 561 dos.writeChar(SIG_INT); 562 dos.writeInt((int) value); 563 } else if (long.class.equals(type)) { 564 dos.writeChar(SIG_LONG); 565 dos.writeLong((long) value); 566 } else if (double.class.equals(type)) { 567 dos.writeChar(SIG_DOUBLE); 568 dos.writeDouble((double) value); 569 } else if (float.class.equals(type)) { 570 dos.writeChar(SIG_FLOAT); 571 dos.writeFloat((float) value); 572 } else if (String.class.equals(type)) { 573 dos.writeChar(SIG_STRING); 574 dos.writeUTF(value != null ? (String) value : ""); 575 } else { 576 dos.writeChar(SIG_VOID); 577 } 578 579 return byteOutStream.toByteArray(); 580 } 581 582 // Prefixes for simple primitives. These match the JNI definitions. 583 private static final char SIG_ARRAY = '['; 584 private static final char SIG_BOOLEAN = 'Z'; 585 private static final char SIG_BYTE = 'B'; 586 private static final char SIG_SHORT = 'S'; 587 private static final char SIG_CHAR = 'C'; 588 private static final char SIG_INT = 'I'; 589 private static final char SIG_LONG = 'J'; 590 private static final char SIG_FLOAT = 'F'; 591 private static final char SIG_DOUBLE = 'D'; 592 private static final char SIG_VOID = 'V'; 593 // Prefixes for some commonly used objects 594 private static final char SIG_STRING = 'R'; 595 596 /** 597 * @hide 598 */ 599 @VisibleForTesting 600 public static class ViewMethodInvocationSerializationException extends Exception { ViewMethodInvocationSerializationException(String message)601 ViewMethodInvocationSerializationException(String message) { 602 super(message); 603 } 604 } 605 } 606