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