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