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.util.Log;
     20 import android.view.View;
     21 import android.view.ViewDebug;
     22 import android.view.ViewRootImpl;
     23 import android.view.WindowManagerGlobal;
     24 
     25 import org.apache.harmony.dalvik.ddmc.Chunk;
     26 import org.apache.harmony.dalvik.ddmc.ChunkHandler;
     27 import org.apache.harmony.dalvik.ddmc.DdmServer;
     28 
     29 import java.io.BufferedWriter;
     30 import java.io.ByteArrayOutputStream;
     31 import java.io.DataOutputStream;
     32 import java.io.IOException;
     33 import java.io.OutputStreamWriter;
     34 import java.lang.reflect.Method;
     35 import java.nio.BufferUnderflowException;
     36 import 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  */
     42 public class DdmHandleViewDebug extends ChunkHandler {
     43     /** 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 
     55     /** Dump View Theme. */
     56     private static final int VURT_DUMP_THEME = 3;
     57 
     58     /**
     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 
     70     /** Profile a view. */
     71     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;
     78 
     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 
     85     /** 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 
     90     private static final DdmHandleViewDebug sInstance = new DdmHandleViewDebug();
     91 
     92     /** singleton, do not instantiate. */
     93     private DdmHandleViewDebug() {}
     94 
     95     public static void register() {
     96         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 
    113         if (type == CHUNK_VULW) {
    114             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);
    130             else if (op == VURT_DUMP_THEME)
    131                 return dumpTheme(rootView);
    132             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);
    147                 case VUOP_PROFILE_VIEW:
    148                     return profileView(rootView, targetView);
    149                 case VUOP_INVOKE_VIEW_METHOD:
    150                     return invokeViewMethod(rootView, targetView, in);
    151                 case VUOP_SET_LAYOUT_PARAMETER:
    152                     return setLayoutParameter(rootView, targetView, in);
    153                 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 
    161     /** 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;
    219         boolean v2 = in.hasRemaining() && in.getInt() > 0;
    220 
    221         long start = System.currentTimeMillis();
    222 
    223         ByteArrayOutputStream b = new ByteArrayOutputStream(2*1024*1024);
    224         try {
    225             if (v2) {
    226                 ViewDebug.dumpv2(rootView, b);
    227             } else {
    228                 ViewDebug.dump(rootView, skipChildren, includeProperties, b);
    229             }
    230         } catch (IOException | InterruptedException e) {
    231             return createFailChunk(1, "Unexpected error while obtaining view hierarchy: "
    232                     + e.getMessage());
    233         }
    234 
    235         long end = System.currentTimeMillis();
    236         Log.d(TAG, "Time to obtain view hierarchy (ms): " + (end - start));
    237 
    238         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 
    263     /**
    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 
    279     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 
    303     /**
    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 
    397         return null;
    398     }
    399 
    400     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 
    412         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 }
    435