• 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