Home | History | Annotate | Download | only in ddm
      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. */
     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         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. */
    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      */
    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 
    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. */
    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      */
    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 
    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. */
    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