• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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