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