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     /**
     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. */
     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);
    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 
    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 
    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      */
    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 
    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. */
    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