DdmHandleViewDebug.java revision e82a4eeb40090f15054e2c76c0cea84b198a2b61
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
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;
35import java.lang.reflect.Method;
36import 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    /** 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. */
97    private DdmHandleViewDebug() {}
98
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
107    public void connected() {
108    }
109
110    @Override
111    public void disconnected() {
112    }
113
114    @Override
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
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. */
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
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
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     */
229    private Chunk dumpHierarchy(View rootView, ByteBuffer in) {
230        boolean skipChildren = in.getInt() > 0;
231        boolean includeProperties = in.getInt() > 0;
232
233        ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
234        try {
235            ViewDebug.dump(rootView, skipChildren, includeProperties, b);
236        } catch (IOException e) {
237            return createFailChunk(1, "Unexpected error while obtaining view hierarchy: "
238                    + e.getMessage());
239        }
240
241        byte[] data = b.toByteArray();
242        return new Chunk(CHUNK_VURT, data, 0, data.length);
243    }
244
245    /** Returns a buffer with region details & bitmap of every single view. */
246    private Chunk captureLayers(View rootView) {
247        ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
248        DataOutputStream dos = new DataOutputStream(b);
249        try {
250            ViewDebug.captureLayers(rootView, dos);
251        } catch (IOException e) {
252            return createFailChunk(1, "Unexpected error while obtaining view hierarchy: "
253                    + e.getMessage());
254        } finally {
255            try {
256                dos.close();
257            } catch (IOException e) {
258                // ignore
259            }
260        }
261
262        byte[] data = b.toByteArray();
263        return new Chunk(CHUNK_VURT, data, 0, data.length);
264    }
265
266    /**
267     * Returns the Theme dump of the provided view.
268     */
269    private Chunk dumpTheme(View rootView) {
270        ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
271        try {
272            ViewDebug.dumpTheme(rootView, b);
273        } catch (IOException e) {
274            return createFailChunk(1, "Unexpected error while dumping the theme: "
275                    + e.getMessage());
276        }
277
278        byte[] data = b.toByteArray();
279        return new Chunk(CHUNK_VURT, data, 0, data.length);
280    }
281
282    private Chunk captureView(View rootView, View targetView) {
283        ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
284        try {
285            ViewDebug.capture(rootView, b, targetView);
286        } catch (IOException e) {
287            return createFailChunk(1, "Unexpected error while capturing view: "
288                    + e.getMessage());
289        }
290
291        byte[] data = b.toByteArray();
292        return new Chunk(CHUNK_VUOP, data, 0, data.length);
293    }
294
295    /** Returns the display lists corresponding to the provided view. */
296    private Chunk dumpDisplayLists(final View rootView, final View targetView) {
297        rootView.post(new Runnable() {
298            @Override
299            public void run() {
300                ViewDebug.outputDisplayList(rootView, targetView);
301            }
302        });
303        return null;
304    }
305
306    /**
307     * Invokes provided method on the view.
308     * The method name and its arguments are passed in as inputs via the byte buffer.
309     * The buffer contains:<ol>
310     *  <li> len(method name) </li>
311     *  <li> method name </li>
312     *  <li> # of args </li>
313     *  <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
314     *          The type specifier is a single character as used in JNI:
315     *          (Z - boolean, B - byte, C - char, S - short, I - int, J - long,
316     *          F - float, D - double). <p>
317     *          The type specifier is followed by the actual value of argument.
318     *          Booleans are encoded via bytes with 0 indicating false.</li>
319     * </ol>
320     * Methods that take no arguments need only specify the method name.
321     */
322    private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
323        int l = in.getInt();
324        String methodName = getString(in, l);
325
326        Class<?>[] argTypes;
327        Object[] args;
328        if (!in.hasRemaining()) {
329            argTypes = new Class<?>[0];
330            args = new Object[0];
331        } else {
332            int nArgs = in.getInt();
333
334            argTypes = new Class<?>[nArgs];
335            args = new Object[nArgs];
336
337            for (int i = 0; i < nArgs; i++) {
338                char c = in.getChar();
339                switch (c) {
340                    case 'Z':
341                        argTypes[i] = boolean.class;
342                        args[i] = in.get() == 0 ? false : true;
343                        break;
344                    case 'B':
345                        argTypes[i] = byte.class;
346                        args[i] = in.get();
347                        break;
348                    case 'C':
349                        argTypes[i] = char.class;
350                        args[i] = in.getChar();
351                        break;
352                    case 'S':
353                        argTypes[i] = short.class;
354                        args[i] = in.getShort();
355                        break;
356                    case 'I':
357                        argTypes[i] = int.class;
358                        args[i] = in.getInt();
359                        break;
360                    case 'J':
361                        argTypes[i] = long.class;
362                        args[i] = in.getLong();
363                        break;
364                    case 'F':
365                        argTypes[i] = float.class;
366                        args[i] = in.getFloat();
367                        break;
368                    case 'D':
369                        argTypes[i] = double.class;
370                        args[i] = in.getDouble();
371                        break;
372                    default:
373                        Log.e(TAG, "arg " + i + ", unrecognized type: " + c);
374                        return createFailChunk(ERR_INVALID_PARAM,
375                                "Unsupported parameter type (" + c + ") to invoke view method.");
376                }
377            }
378        }
379
380        Method method = null;
381        try {
382            method = targetView.getClass().getMethod(methodName, argTypes);
383        } catch (NoSuchMethodException e) {
384            Log.e(TAG, "No such method: " + e.getMessage());
385            return createFailChunk(ERR_INVALID_PARAM,
386                    "No such method: " + e.getMessage());
387        }
388
389        try {
390            ViewDebug.invokeViewMethod(targetView, method, args);
391        } catch (Exception e) {
392            Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
393            String msg = e.getCause().getMessage();
394            if (msg == null) {
395                msg = e.getCause().toString();
396            }
397            return createFailChunk(ERR_EXCEPTION, msg);
398        }
399
400        return null;
401    }
402
403    private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) {
404        int l = in.getInt();
405        String param = getString(in, l);
406        int value = in.getInt();
407        try {
408            ViewDebug.setLayoutParameter(targetView, param, value);
409        } catch (Exception e) {
410            Log.e(TAG, "Exception setting layout parameter: " + e);
411            return createFailChunk(ERR_EXCEPTION, "Error accessing field "
412                        + param + ":" + e.getMessage());
413        }
414
415        return null;
416    }
417
418    /** Profiles provided view. */
419    private Chunk profileView(View rootView, final View targetView) {
420        ByteArrayOutputStream b = new ByteArrayOutputStream(32 * 1024);
421        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(b), 32 * 1024);
422        try {
423            ViewDebug.profileViewAndChildren(targetView, bw);
424        } catch (IOException e) {
425            return createFailChunk(1, "Unexpected error while profiling view: " + e.getMessage());
426        } finally {
427            try {
428                bw.close();
429            } catch (IOException e) {
430                // ignore
431            }
432        }
433
434        byte[] data = b.toByteArray();
435        return new Chunk(CHUNK_VUOP, data, 0, data.length);
436    }
437}
438