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.nio.BufferUnderflowException; 35 import java.nio.ByteBuffer; 36 37 /** 38 * Handle various requests related to profiling / debugging of the view system. 39 * Support for these features are advertised via {@link DdmHandleHello}. 40 */ 41 public class DdmHandleViewDebug extends DdmHandle { 42 /** List {@link ViewRootImpl}'s of this process. */ 43 private static final int CHUNK_VULW = ChunkHandler.type("VULW"); 44 45 /** Operation on view root, first parameter in packet should be one of VURT_* constants */ 46 private static final int CHUNK_VURT = ChunkHandler.type("VURT"); 47 48 /** Dump view hierarchy. */ 49 private static final int VURT_DUMP_HIERARCHY = 1; 50 51 /** Capture View Layers. */ 52 private static final int VURT_CAPTURE_LAYERS = 2; 53 54 /** Dump View Theme. */ 55 private static final int VURT_DUMP_THEME = 3; 56 57 /** 58 * Generic View Operation, first parameter in the packet should be one of the 59 * VUOP_* constants below. 60 */ 61 private static final int CHUNK_VUOP = ChunkHandler.type("VUOP"); 62 63 /** Capture View. */ 64 private static final int VUOP_CAPTURE_VIEW = 1; 65 66 /** Obtain the Display List corresponding to the view. */ 67 private static final int VUOP_DUMP_DISPLAYLIST = 2; 68 69 /** Profile a view. */ 70 private static final int VUOP_PROFILE_VIEW = 3; 71 72 /** Invoke a method on the view. */ 73 private static final int VUOP_INVOKE_VIEW_METHOD = 4; 74 75 /** Set layout parameter. */ 76 private static final int VUOP_SET_LAYOUT_PARAMETER = 5; 77 78 /** Error code indicating operation specified in chunk is invalid. */ 79 private static final int ERR_INVALID_OP = -1; 80 81 /** Error code indicating that the parameters are invalid. */ 82 private static final int ERR_INVALID_PARAM = -2; 83 84 /** Error code indicating an exception while performing operation. */ 85 private static final int ERR_EXCEPTION = -3; 86 87 private static final String TAG = "DdmViewDebug"; 88 89 private static final DdmHandleViewDebug sInstance = new DdmHandleViewDebug(); 90 91 /** singleton, do not instantiate. */ DdmHandleViewDebug()92 private DdmHandleViewDebug() {} 93 register()94 public static void register() { 95 DdmServer.registerHandler(CHUNK_VULW, sInstance); 96 DdmServer.registerHandler(CHUNK_VURT, sInstance); 97 DdmServer.registerHandler(CHUNK_VUOP, sInstance); 98 } 99 100 @Override onConnected()101 public void onConnected() { 102 } 103 104 @Override onDisconnected()105 public void onDisconnected() { 106 } 107 108 @Override handleChunk(Chunk request)109 public Chunk handleChunk(Chunk request) { 110 int type = request.type; 111 112 if (type == CHUNK_VULW) { 113 return listWindows(); 114 } 115 116 ByteBuffer in = wrapChunk(request); 117 int op = in.getInt(); 118 119 View rootView = getRootView(in); 120 if (rootView == null) { 121 return createFailChunk(ERR_INVALID_PARAM, "Invalid View Root"); 122 } 123 124 if (type == CHUNK_VURT) { 125 if (op == VURT_DUMP_HIERARCHY) { 126 return dumpHierarchy(rootView, in); 127 } else if (op == VURT_CAPTURE_LAYERS) { 128 return captureLayers(rootView); 129 } else if (op == VURT_DUMP_THEME) { 130 return dumpTheme(rootView); 131 } else { 132 return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op); 133 } 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 " + 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 (encoded as UTF-16 2-byte characters) </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 one character modelled after JNI signatures: 312 * <ul> 313 * <li>[ - array<br> 314 * This is followed by a second character according to this spec, indicating the 315 * array type, then the array length as an Int, followed by a repeated encoding 316 * of the actual data. 317 * WARNING: Only <b>byte[]</b> is supported currently. 318 * </li> 319 * <li>Z - boolean<br> 320 * Booleans are encoded via bytes with 0 indicating false</li> 321 * <li>B - byte</li> 322 * <li>C - char</li> 323 * <li>S - short</li> 324 * <li>I - int</li> 325 * <li>J - long</li> 326 * <li>F - float</li> 327 * <li>D - double</li> 328 * <li>V - void<br> 329 * NOT followed by a value. Only used for return types</li> 330 * <li>R - String (not a real JNI type, but added for convenience)<br> 331 * Strings are encoded as an unsigned short of the number of <b>bytes</b>, 332 * followed by the actual UTF-8 encoded bytes. 333 * WARNING: This is the same encoding as produced by 334 * ViewHierarchyEncoder#writeString. However, note that this encoding is 335 * different to what DdmHandle#getString() expects, which is used in other places 336 * in this class. 337 * WARNING: Since the length is the number of UTF-8 encoded bytes, Strings can 338 * contain up to 64k ASCII characters, yet depending on the actual data, the true 339 * maximum might be as little as 21844 unicode characters. 340 * <b>null</b> String objects are encoded as an empty string 341 * </li> 342 * </ul> 343 * </li> 344 * </ol> 345 * Methods that take no arguments need only specify the method name. 346 * 347 * The return value is encoded the same way as a single parameter (type + value) 348 */ invokeViewMethod(View rootView, final View targetView, ByteBuffer in)349 private Chunk invokeViewMethod(View rootView, final View targetView, ByteBuffer in) { 350 int l = in.getInt(); 351 String methodName = getString(in, l); 352 353 try { 354 byte[] returnValue = ViewDebug.invokeViewMethod(targetView, methodName, in); 355 return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length); 356 } catch (ViewDebug.ViewMethodInvocationSerializationException e) { 357 return createFailChunk(ERR_INVALID_PARAM, e.getMessage()); 358 } catch (Exception e) { 359 return createFailChunk(ERR_EXCEPTION, e.getMessage()); 360 } 361 } 362 setLayoutParameter(final View rootView, final View targetView, ByteBuffer in)363 private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) { 364 int l = in.getInt(); 365 String param = getString(in, l); 366 int value = in.getInt(); 367 try { 368 ViewDebug.setLayoutParameter(targetView, param, value); 369 } catch (Exception e) { 370 Log.e(TAG, "Exception setting layout parameter: " + e); 371 return createFailChunk(ERR_EXCEPTION, "Error accessing field " 372 + param + ":" + e.getMessage()); 373 } 374 375 return null; 376 } 377 378 /** Profiles provided view. */ profileView(View rootView, final View targetView)379 private Chunk profileView(View rootView, final View targetView) { 380 ByteArrayOutputStream b = new ByteArrayOutputStream(32 * 1024); 381 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(b), 32 * 1024); 382 try { 383 ViewDebug.profileViewAndChildren(targetView, bw); 384 } catch (IOException e) { 385 return createFailChunk(1, "Unexpected error while profiling view: " + e.getMessage()); 386 } finally { 387 try { 388 bw.close(); 389 } catch (IOException e) { 390 // ignore 391 } 392 } 393 394 byte[] data = b.toByteArray(); 395 return new Chunk(CHUNK_VUOP, data, 0, data.length); 396 } 397 } 398