blob: ce837966c12be6bffce42f3f930eddf181dec823 [file] [log] [blame]
Siva Velusamy945bfb62013-01-06 16:03:12 -08001/*
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
17package android.ddm;
18
19import android.opengl.GLUtils;
20import android.util.Log;
21import android.view.View;
22import android.view.ViewDebug;
23import android.view.ViewRootImpl;
24import android.view.WindowManagerGlobal;
25
26import org.apache.harmony.dalvik.ddmc.Chunk;
27import org.apache.harmony.dalvik.ddmc.ChunkHandler;
28import org.apache.harmony.dalvik.ddmc.DdmServer;
29
30import java.io.BufferedWriter;
31import java.io.ByteArrayOutputStream;
32import java.io.DataOutputStream;
33import java.io.IOException;
34import java.io.OutputStreamWriter;
Siva Velusamyf9455fa2013-01-17 18:01:52 -080035import java.lang.reflect.Method;
Siva Velusamy945bfb62013-01-06 16:03:12 -080036import java.nio.BufferUnderflowException;
37import 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 */
43public 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
Siva Velusamy945bfb62013-01-06 16:03:12 -080071 /** Profile a view. */
Siva Velusamyf9455fa2013-01-17 18:01:52 -080072 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;
Siva Velusamy945bfb62013-01-06 16:03:12 -080079
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
Siva Velusamyf9455fa2013-01-17 18:01:52 -080086 /** 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
Siva Velusamy945bfb62013-01-06 16:03:12 -080091 private static final DdmHandleViewDebug sInstance = new DdmHandleViewDebug();
92
93 /** singleton, do not instantiate. */
94 private DdmHandleViewDebug() {}
95
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
104 public void connected() {
105 }
106
107 @Override
108 public void disconnected() {
109 }
110
111 @Override
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);
Siva Velusamy945bfb62013-01-06 16:03:12 -0800149 case VUOP_PROFILE_VIEW:
150 return profileView(rootView, targetView);
Siva Velusamyf9455fa2013-01-17 18:01:52 -0800151 case VUOP_INVOKE_VIEW_METHOD:
152 return invokeViewMethod(rootView, targetView, in);
153 case VUOP_SET_LAYOUT_PARAMETER:
154 return setLayoutParameter(rootView, targetView, in);
Siva Velusamy945bfb62013-01-06 16:03:12 -0800155 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
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. */
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
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
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 */
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. */
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
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. */
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
Siva Velusamyf9455fa2013-01-17 18:01:52 -0800285 /**
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 */
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
Siva Velusamy945bfb62013-01-06 16:03:12 -0800379 return null;
380 }
381
Siva Velusamyf9455fa2013-01-17 18:01:52 -0800382 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
Siva Velusamy945bfb62013-01-06 16:03:12 -0800394 return null;
395 }
396
397 /** Profiles provided view. */
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}