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. */ DdmHandleViewDebug()93 private DdmHandleViewDebug() {} 94 register()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 connected()102 public void connected() { 103 } 104 105 @Override disconnected()106 public void disconnected() { 107 } 108 109 @Override handleChunk(Chunk request)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. */ listWindows()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 getRootView(ByteBuffer in)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 getTargetView(View root, ByteBuffer in)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 */ dumpHierarchy(View rootView, ByteBuffer in)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. */ captureLayers(View rootView)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 */ dumpTheme(View rootView)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 captureView(View rootView, View targetView)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. */ dumpDisplayLists(final View rootView, final View targetView)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 */ invokeViewMethod(final View rootView, final View targetView, ByteBuffer in)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 setLayoutParameter(final View rootView, final View targetView, ByteBuffer in)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. */ profileView(View rootView, final View targetView)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