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. */ DdmHandleViewDebug()97 private DdmHandleViewDebug() {} 98 register()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 connected()107 public void connected() { 108 } 109 110 @Override disconnected()111 public void disconnected() { 112 } 113 114 @Override handleChunk(Chunk request)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 handleOpenGlTrace(Chunk request)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. */ listWindows()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 getRootView(ByteBuffer in)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 getTargetView(View root, ByteBuffer in)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 */ dumpHierarchy(View rootView, ByteBuffer in)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. */ captureLayers(View rootView)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 */ dumpTheme(View rootView)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 captureView(View rootView, View targetView)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. */ dumpDisplayLists(final View rootView, final View targetView)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 */ invokeViewMethod(final View rootView, final View targetView, ByteBuffer in)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 setLayoutParameter(final View rootView, final View targetView, ByteBuffer in)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. */ profileView(View rootView, final View targetView)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