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 android.opengl.GLUtils;
20 import android.util.Log;
21 import android.view.View;
22 import android.view.ViewDebug;
23 import android.view.ViewRootImpl;
24 import android.view.WindowManagerGlobal;
25 
26 import org.apache.harmony.dalvik.ddmc.Chunk;
27 import org.apache.harmony.dalvik.ddmc.ChunkHandler;
28 import org.apache.harmony.dalvik.ddmc.DdmServer;
29 
30 import java.io.BufferedWriter;
31 import java.io.ByteArrayOutputStream;
32 import java.io.DataOutputStream;
33 import java.io.IOException;
34 import java.io.OutputStreamWriter;
35 import java.lang.reflect.Method;
36 import java.nio.BufferUnderflowException;
37 import java.nio.ByteBuffer;
38 
39 /**
40  * Handle various requests related to profiling / debugging of the view system.
41  * Support for these features are advertised via {@link DdmHandleHello}.
42  */
43 public class DdmHandleViewDebug extends ChunkHandler {
44     /** Enable/Disable tracing of OpenGL calls. */
45     public static final int CHUNK_VUGL = type("VUGL");
46 
47     /** List {@link ViewRootImpl}'s of this process. */
48     private static final int CHUNK_VULW = type("VULW");
49 
50     /** Operation on view root, first parameter in packet should be one of VURT_* constants */
51     private static final int CHUNK_VURT = type("VURT");
52 
53     /** Dump view hierarchy. */
54     private static final int VURT_DUMP_HIERARCHY = 1;
55 
56     /** Capture View Layers. */
57     private static final int VURT_CAPTURE_LAYERS = 2;
58 
59     /** Dump View Theme. */
60     private static final int VURT_DUMP_THEME = 3;
61 
62     /**
63      * Generic View Operation, first parameter in the packet should be one of the
64      * VUOP_* constants below.
65      */
66     private static final int CHUNK_VUOP = type("VUOP");
67 
68     /** Capture View. */
69     private static final int VUOP_CAPTURE_VIEW = 1;
70 
71     /** Obtain the Display List corresponding to the view. */
72     private static final int VUOP_DUMP_DISPLAYLIST = 2;
73 
74     /** Profile a view. */
75     private static final int VUOP_PROFILE_VIEW = 3;
76 
77     /** Invoke a method on the view. */
78     private static final int VUOP_INVOKE_VIEW_METHOD = 4;
79 
80     /** Set layout parameter. */
81     private static final int VUOP_SET_LAYOUT_PARAMETER = 5;
82 
83     /** Error code indicating operation specified in chunk is invalid. */
84     private static final int ERR_INVALID_OP = -1;
85 
86     /** Error code indicating that the parameters are invalid. */
87     private static final int ERR_INVALID_PARAM = -2;
88 
89     /** Error code indicating an exception while performing operation. */
90     private static final int ERR_EXCEPTION = -3;
91 
92     private static final String TAG = "DdmViewDebug";
93 
94     private static final DdmHandleViewDebug sInstance = new DdmHandleViewDebug();
95 
96     /** singleton, do not instantiate. */
DdmHandleViewDebug()97     private DdmHandleViewDebug() {}
98 
register()99     public static void register() {
100         DdmServer.registerHandler(CHUNK_VUGL, sInstance);
101         DdmServer.registerHandler(CHUNK_VULW, sInstance);
102         DdmServer.registerHandler(CHUNK_VURT, sInstance);
103         DdmServer.registerHandler(CHUNK_VUOP, sInstance);
104     }
105 
106     @Override
connected()107     public void connected() {
108     }
109 
110     @Override
disconnected()111     public void disconnected() {
112     }
113 
114     @Override
handleChunk(Chunk request)115     public Chunk handleChunk(Chunk request) {
116         int type = request.type;
117 
118         if (type == CHUNK_VUGL) {
119             return handleOpenGlTrace(request);
120         } else if (type == CHUNK_VULW) {
121             return listWindows();
122         }
123 
124         ByteBuffer in = wrapChunk(request);
125         int op = in.getInt();
126 
127         View rootView = getRootView(in);
128         if (rootView == null) {
129             return createFailChunk(ERR_INVALID_PARAM, "Invalid View Root");
130         }
131 
132         if (type == CHUNK_VURT) {
133             if (op == VURT_DUMP_HIERARCHY)
134                 return dumpHierarchy(rootView, in);
135             else if (op == VURT_CAPTURE_LAYERS)
136                 return captureLayers(rootView);
137             else if (op == VURT_DUMP_THEME)
138                 return dumpTheme(rootView);
139             else
140                 return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op);
141         }
142 
143         final View targetView = getTargetView(rootView, in);
144         if (targetView == null) {
145             return createFailChunk(ERR_INVALID_PARAM, "Invalid target view");
146         }
147 
148         if (type == CHUNK_VUOP) {
149             switch (op) {
150                 case VUOP_CAPTURE_VIEW:
151                     return captureView(rootView, targetView);
152                 case VUOP_DUMP_DISPLAYLIST:
153                     return dumpDisplayLists(rootView, targetView);
154                 case VUOP_PROFILE_VIEW:
155                     return profileView(rootView, targetView);
156                 case VUOP_INVOKE_VIEW_METHOD:
157                     return invokeViewMethod(rootView, targetView, in);
158                 case VUOP_SET_LAYOUT_PARAMETER:
159                     return setLayoutParameter(rootView, targetView, in);
160                 default:
161                     return createFailChunk(ERR_INVALID_OP, "Unknown view operation: " + op);
162             }
163         } else {
164             throw new RuntimeException("Unknown packet " + ChunkHandler.name(type));
165         }
166     }
167 
handleOpenGlTrace(Chunk request)168     private Chunk handleOpenGlTrace(Chunk request) {
169         ByteBuffer in = wrapChunk(request);
170         GLUtils.setTracingLevel(in.getInt());
171         return null;    // empty response
172     }
173 
174     /** Returns the list of windows owned by this client. */
listWindows()175     private Chunk listWindows() {
176         String[] windowNames = WindowManagerGlobal.getInstance().getViewRootNames();
177 
178         int responseLength = 4;                     // # of windows
179         for (String name : windowNames) {
180             responseLength += 4;                    // length of next window name
181             responseLength += name.length() * 2;    // window name
182         }
183 
184         ByteBuffer out = ByteBuffer.allocate(responseLength);
185         out.order(ChunkHandler.CHUNK_ORDER);
186 
187         out.putInt(windowNames.length);
188         for (String name : windowNames) {
189             out.putInt(name.length());
190             putString(out, name);
191         }
192 
193         return new Chunk(CHUNK_VULW, out);
194     }
195 
getRootView(ByteBuffer in)196     private View getRootView(ByteBuffer in) {
197         try {
198             int viewRootNameLength = in.getInt();
199             String viewRootName = getString(in, viewRootNameLength);
200             return WindowManagerGlobal.getInstance().getRootView(viewRootName);
201         } catch (BufferUnderflowException e) {
202             return null;
203         }
204     }
205 
getTargetView(View root, ByteBuffer in)206     private View getTargetView(View root, ByteBuffer in) {
207         int viewLength;
208         String viewName;
209 
210         try {
211             viewLength = in.getInt();
212             viewName = getString(in, viewLength);
213         } catch (BufferUnderflowException e) {
214             return null;
215         }
216 
217         return ViewDebug.findView(root, viewName);
218     }
219 
220     /**
221      * Returns the view hierarchy and/or view properties starting at the provided view.
222      * Based on the input options, the return data may include:
223      *  - just the view hierarchy
224      *  - view hierarchy & the properties for each of the views
225      *  - just the view properties for a specific view.
226      *  TODO: Currently this only returns views starting at the root, need to fix so that
227      *  it can return properties of any view.
228      */
dumpHierarchy(View rootView, ByteBuffer in)229     private Chunk dumpHierarchy(View rootView, ByteBuffer in) {
230         boolean skipChildren = in.getInt() > 0;
231         boolean includeProperties = in.getInt() > 0;
232         boolean v2 = in.hasRemaining() && in.getInt() > 0;
233 
234         long start = System.currentTimeMillis();
235 
236         ByteArrayOutputStream b = new ByteArrayOutputStream(2*1024*1024);
237         try {
238             if (v2) {
239                 ViewDebug.dumpv2(rootView, b);
240             } else {
241                 ViewDebug.dump(rootView, skipChildren, includeProperties, b);
242             }
243         } catch (IOException | InterruptedException e) {
244             return createFailChunk(1, "Unexpected error while obtaining view hierarchy: "
245                     + e.getMessage());
246         }
247 
248         long end = System.currentTimeMillis();
249         Log.d(TAG, "Time to obtain view hierarchy (ms): " + (end - start));
250 
251         byte[] data = b.toByteArray();
252         return new Chunk(CHUNK_VURT, data, 0, data.length);
253     }
254 
255     /** Returns a buffer with region details & bitmap of every single view. */
captureLayers(View rootView)256     private Chunk captureLayers(View rootView) {
257         ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
258         DataOutputStream dos = new DataOutputStream(b);
259         try {
260             ViewDebug.captureLayers(rootView, dos);
261         } catch (IOException e) {
262             return createFailChunk(1, "Unexpected error while obtaining view hierarchy: "
263                     + e.getMessage());
264         } finally {
265             try {
266                 dos.close();
267             } catch (IOException e) {
268                 // ignore
269             }
270         }
271 
272         byte[] data = b.toByteArray();
273         return new Chunk(CHUNK_VURT, data, 0, data.length);
274     }
275 
276     /**
277      * Returns the Theme dump of the provided view.
278      */
dumpTheme(View rootView)279     private Chunk dumpTheme(View rootView) {
280         ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
281         try {
282             ViewDebug.dumpTheme(rootView, b);
283         } catch (IOException e) {
284             return createFailChunk(1, "Unexpected error while dumping the theme: "
285                     + e.getMessage());
286         }
287 
288         byte[] data = b.toByteArray();
289         return new Chunk(CHUNK_VURT, data, 0, data.length);
290     }
291 
captureView(View rootView, View targetView)292     private Chunk captureView(View rootView, View targetView) {
293         ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
294         try {
295             ViewDebug.capture(rootView, b, targetView);
296         } catch (IOException e) {
297             return createFailChunk(1, "Unexpected error while capturing view: "
298                     + e.getMessage());
299         }
300 
301         byte[] data = b.toByteArray();
302         return new Chunk(CHUNK_VUOP, data, 0, data.length);
303     }
304 
305     /** Returns the display lists corresponding to the provided view. */
dumpDisplayLists(final View rootView, final View targetView)306     private Chunk dumpDisplayLists(final View rootView, final View targetView) {
307         rootView.post(new Runnable() {
308             @Override
309             public void run() {
310                 ViewDebug.outputDisplayList(rootView, targetView);
311             }
312         });
313         return null;
314     }
315 
316     /**
317      * Invokes provided method on the view.
318      * The method name and its arguments are passed in as inputs via the byte buffer.
319      * The buffer contains:<ol>
320      *  <li> len(method name) </li>
321      *  <li> method name </li>
322      *  <li> # of args </li>
323      *  <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
324      *          The type specifier is a single character as used in JNI:
325      *          (Z - boolean, B - byte, C - char, S - short, I - int, J - long,
326      *          F - float, D - double). <p>
327      *          The type specifier is followed by the actual value of argument.
328      *          Booleans are encoded via bytes with 0 indicating false.</li>
329      * </ol>
330      * Methods that take no arguments need only specify the method name.
331      */
invokeViewMethod(final View rootView, final View targetView, ByteBuffer in)332     private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
333         int l = in.getInt();
334         String methodName = getString(in, l);
335 
336         Class<?>[] argTypes;
337         Object[] args;
338         if (!in.hasRemaining()) {
339             argTypes = new Class<?>[0];
340             args = new Object[0];
341         } else {
342             int nArgs = in.getInt();
343 
344             argTypes = new Class<?>[nArgs];
345             args = new Object[nArgs];
346 
347             for (int i = 0; i < nArgs; i++) {
348                 char c = in.getChar();
349                 switch (c) {
350                     case 'Z':
351                         argTypes[i] = boolean.class;
352                         args[i] = in.get() == 0 ? false : true;
353                         break;
354                     case 'B':
355                         argTypes[i] = byte.class;
356                         args[i] = in.get();
357                         break;
358                     case 'C':
359                         argTypes[i] = char.class;
360                         args[i] = in.getChar();
361                         break;
362                     case 'S':
363                         argTypes[i] = short.class;
364                         args[i] = in.getShort();
365                         break;
366                     case 'I':
367                         argTypes[i] = int.class;
368                         args[i] = in.getInt();
369                         break;
370                     case 'J':
371                         argTypes[i] = long.class;
372                         args[i] = in.getLong();
373                         break;
374                     case 'F':
375                         argTypes[i] = float.class;
376                         args[i] = in.getFloat();
377                         break;
378                     case 'D':
379                         argTypes[i] = double.class;
380                         args[i] = in.getDouble();
381                         break;
382                     default:
383                         Log.e(TAG, "arg " + i + ", unrecognized type: " + c);
384                         return createFailChunk(ERR_INVALID_PARAM,
385                                 "Unsupported parameter type (" + c + ") to invoke view method.");
386                 }
387             }
388         }
389 
390         Method method = null;
391         try {
392             method = targetView.getClass().getMethod(methodName, argTypes);
393         } catch (NoSuchMethodException e) {
394             Log.e(TAG, "No such method: " + e.getMessage());
395             return createFailChunk(ERR_INVALID_PARAM,
396                     "No such method: " + e.getMessage());
397         }
398 
399         try {
400             ViewDebug.invokeViewMethod(targetView, method, args);
401         } catch (Exception e) {
402             Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
403             String msg = e.getCause().getMessage();
404             if (msg == null) {
405                 msg = e.getCause().toString();
406             }
407             return createFailChunk(ERR_EXCEPTION, msg);
408         }
409 
410         return null;
411     }
412 
setLayoutParameter(final View rootView, final View targetView, ByteBuffer in)413     private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) {
414         int l = in.getInt();
415         String param = getString(in, l);
416         int value = in.getInt();
417         try {
418             ViewDebug.setLayoutParameter(targetView, param, value);
419         } catch (Exception e) {
420             Log.e(TAG, "Exception setting layout parameter: " + e);
421             return createFailChunk(ERR_EXCEPTION, "Error accessing field "
422                         + param + ":" + e.getMessage());
423         }
424 
425         return null;
426     }
427 
428     /** Profiles provided view. */
profileView(View rootView, final View targetView)429     private Chunk profileView(View rootView, final View targetView) {
430         ByteArrayOutputStream b = new ByteArrayOutputStream(32 * 1024);
431         BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(b), 32 * 1024);
432         try {
433             ViewDebug.profileViewAndChildren(targetView, bw);
434         } catch (IOException e) {
435             return createFailChunk(1, "Unexpected error while profiling view: " + e.getMessage());
436         } finally {
437             try {
438                 bw.close();
439             } catch (IOException e) {
440                 // ignore
441             }
442         }
443 
444         byte[] data = b.toByteArray();
445         return new Chunk(CHUNK_VUOP, data, 0, data.length);
446     }
447 }
448