|  | /* | 
|  | * Copyright (C) 2013 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | package android.ddm; | 
|  |  | 
|  | import android.util.Log; | 
|  | import android.view.View; | 
|  | import android.view.ViewDebug; | 
|  | import android.view.ViewRootImpl; | 
|  | import android.view.WindowManagerGlobal; | 
|  |  | 
|  | import org.apache.harmony.dalvik.ddmc.Chunk; | 
|  | import org.apache.harmony.dalvik.ddmc.ChunkHandler; | 
|  | import org.apache.harmony.dalvik.ddmc.DdmServer; | 
|  |  | 
|  | import java.io.BufferedWriter; | 
|  | import java.io.ByteArrayOutputStream; | 
|  | import java.io.DataOutputStream; | 
|  | import java.io.IOException; | 
|  | import java.io.OutputStreamWriter; | 
|  | import java.lang.reflect.Method; | 
|  | import java.nio.BufferUnderflowException; | 
|  | import java.nio.ByteBuffer; | 
|  |  | 
|  | /** | 
|  | * Handle various requests related to profiling / debugging of the view system. | 
|  | * Support for these features are advertised via {@link DdmHandleHello}. | 
|  | */ | 
|  | public class DdmHandleViewDebug extends ChunkHandler { | 
|  | /** List {@link ViewRootImpl}'s of this process. */ | 
|  | private static final int CHUNK_VULW = type("VULW"); | 
|  |  | 
|  | /** Operation on view root, first parameter in packet should be one of VURT_* constants */ | 
|  | private static final int CHUNK_VURT = type("VURT"); | 
|  |  | 
|  | /** Dump view hierarchy. */ | 
|  | private static final int VURT_DUMP_HIERARCHY = 1; | 
|  |  | 
|  | /** Capture View Layers. */ | 
|  | private static final int VURT_CAPTURE_LAYERS = 2; | 
|  |  | 
|  | /** Dump View Theme. */ | 
|  | private static final int VURT_DUMP_THEME = 3; | 
|  |  | 
|  | /** | 
|  | * Generic View Operation, first parameter in the packet should be one of the | 
|  | * VUOP_* constants below. | 
|  | */ | 
|  | private static final int CHUNK_VUOP = type("VUOP"); | 
|  |  | 
|  | /** Capture View. */ | 
|  | private static final int VUOP_CAPTURE_VIEW = 1; | 
|  |  | 
|  | /** Obtain the Display List corresponding to the view. */ | 
|  | private static final int VUOP_DUMP_DISPLAYLIST = 2; | 
|  |  | 
|  | /** Profile a view. */ | 
|  | private static final int VUOP_PROFILE_VIEW = 3; | 
|  |  | 
|  | /** Invoke a method on the view. */ | 
|  | private static final int VUOP_INVOKE_VIEW_METHOD = 4; | 
|  |  | 
|  | /** Set layout parameter. */ | 
|  | private static final int VUOP_SET_LAYOUT_PARAMETER = 5; | 
|  |  | 
|  | /** Error code indicating operation specified in chunk is invalid. */ | 
|  | private static final int ERR_INVALID_OP = -1; | 
|  |  | 
|  | /** Error code indicating that the parameters are invalid. */ | 
|  | private static final int ERR_INVALID_PARAM = -2; | 
|  |  | 
|  | /** Error code indicating an exception while performing operation. */ | 
|  | private static final int ERR_EXCEPTION = -3; | 
|  |  | 
|  | private static final String TAG = "DdmViewDebug"; | 
|  |  | 
|  | private static final DdmHandleViewDebug sInstance = new DdmHandleViewDebug(); | 
|  |  | 
|  | /** singleton, do not instantiate. */ | 
|  | private DdmHandleViewDebug() {} | 
|  |  | 
|  | public static void register() { | 
|  | DdmServer.registerHandler(CHUNK_VULW, sInstance); | 
|  | DdmServer.registerHandler(CHUNK_VURT, sInstance); | 
|  | DdmServer.registerHandler(CHUNK_VUOP, sInstance); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void connected() { | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void disconnected() { | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Chunk handleChunk(Chunk request) { | 
|  | int type = request.type; | 
|  |  | 
|  | if (type == CHUNK_VULW) { | 
|  | return listWindows(); | 
|  | } | 
|  |  | 
|  | ByteBuffer in = wrapChunk(request); | 
|  | int op = in.getInt(); | 
|  |  | 
|  | View rootView = getRootView(in); | 
|  | if (rootView == null) { | 
|  | return createFailChunk(ERR_INVALID_PARAM, "Invalid View Root"); | 
|  | } | 
|  |  | 
|  | if (type == CHUNK_VURT) { | 
|  | if (op == VURT_DUMP_HIERARCHY) | 
|  | return dumpHierarchy(rootView, in); | 
|  | else if (op == VURT_CAPTURE_LAYERS) | 
|  | return captureLayers(rootView); | 
|  | else if (op == VURT_DUMP_THEME) | 
|  | return dumpTheme(rootView); | 
|  | else | 
|  | return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op); | 
|  | } | 
|  |  | 
|  | final View targetView = getTargetView(rootView, in); | 
|  | if (targetView == null) { | 
|  | return createFailChunk(ERR_INVALID_PARAM, "Invalid target view"); | 
|  | } | 
|  |  | 
|  | if (type == CHUNK_VUOP) { | 
|  | switch (op) { | 
|  | case VUOP_CAPTURE_VIEW: | 
|  | return captureView(rootView, targetView); | 
|  | case VUOP_DUMP_DISPLAYLIST: | 
|  | return dumpDisplayLists(rootView, targetView); | 
|  | case VUOP_PROFILE_VIEW: | 
|  | return profileView(rootView, targetView); | 
|  | case VUOP_INVOKE_VIEW_METHOD: | 
|  | return invokeViewMethod(rootView, targetView, in); | 
|  | case VUOP_SET_LAYOUT_PARAMETER: | 
|  | return setLayoutParameter(rootView, targetView, in); | 
|  | default: | 
|  | return createFailChunk(ERR_INVALID_OP, "Unknown view operation: " + op); | 
|  | } | 
|  | } else { | 
|  | throw new RuntimeException("Unknown packet " + ChunkHandler.name(type)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Returns the list of windows owned by this client. */ | 
|  | private Chunk listWindows() { | 
|  | String[] windowNames = WindowManagerGlobal.getInstance().getViewRootNames(); | 
|  |  | 
|  | int responseLength = 4;                     // # of windows | 
|  | for (String name : windowNames) { | 
|  | responseLength += 4;                    // length of next window name | 
|  | responseLength += name.length() * 2;    // window name | 
|  | } | 
|  |  | 
|  | ByteBuffer out = ByteBuffer.allocate(responseLength); | 
|  | out.order(ChunkHandler.CHUNK_ORDER); | 
|  |  | 
|  | out.putInt(windowNames.length); | 
|  | for (String name : windowNames) { | 
|  | out.putInt(name.length()); | 
|  | putString(out, name); | 
|  | } | 
|  |  | 
|  | return new Chunk(CHUNK_VULW, out); | 
|  | } | 
|  |  | 
|  | private View getRootView(ByteBuffer in) { | 
|  | try { | 
|  | int viewRootNameLength = in.getInt(); | 
|  | String viewRootName = getString(in, viewRootNameLength); | 
|  | return WindowManagerGlobal.getInstance().getRootView(viewRootName); | 
|  | } catch (BufferUnderflowException e) { | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | private View getTargetView(View root, ByteBuffer in) { | 
|  | int viewLength; | 
|  | String viewName; | 
|  |  | 
|  | try { | 
|  | viewLength = in.getInt(); | 
|  | viewName = getString(in, viewLength); | 
|  | } catch (BufferUnderflowException e) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | return ViewDebug.findView(root, viewName); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the view hierarchy and/or view properties starting at the provided view. | 
|  | * Based on the input options, the return data may include: | 
|  | *  - just the view hierarchy | 
|  | *  - view hierarchy & the properties for each of the views | 
|  | *  - just the view properties for a specific view. | 
|  | *  TODO: Currently this only returns views starting at the root, need to fix so that | 
|  | *  it can return properties of any view. | 
|  | */ | 
|  | private Chunk dumpHierarchy(View rootView, ByteBuffer in) { | 
|  | boolean skipChildren = in.getInt() > 0; | 
|  | boolean includeProperties = in.getInt() > 0; | 
|  | boolean v2 = in.hasRemaining() && in.getInt() > 0; | 
|  |  | 
|  | long start = System.currentTimeMillis(); | 
|  |  | 
|  | ByteArrayOutputStream b = new ByteArrayOutputStream(2*1024*1024); | 
|  | try { | 
|  | if (v2) { | 
|  | ViewDebug.dumpv2(rootView, b); | 
|  | } else { | 
|  | ViewDebug.dump(rootView, skipChildren, includeProperties, b); | 
|  | } | 
|  | } catch (IOException | InterruptedException e) { | 
|  | return createFailChunk(1, "Unexpected error while obtaining view hierarchy: " | 
|  | + e.getMessage()); | 
|  | } | 
|  |  | 
|  | long end = System.currentTimeMillis(); | 
|  | Log.d(TAG, "Time to obtain view hierarchy (ms): " + (end - start)); | 
|  |  | 
|  | byte[] data = b.toByteArray(); | 
|  | return new Chunk(CHUNK_VURT, data, 0, data.length); | 
|  | } | 
|  |  | 
|  | /** Returns a buffer with region details & bitmap of every single view. */ | 
|  | private Chunk captureLayers(View rootView) { | 
|  | ByteArrayOutputStream b = new ByteArrayOutputStream(1024); | 
|  | DataOutputStream dos = new DataOutputStream(b); | 
|  | try { | 
|  | ViewDebug.captureLayers(rootView, dos); | 
|  | } catch (IOException e) { | 
|  | return createFailChunk(1, "Unexpected error while obtaining view hierarchy: " | 
|  | + e.getMessage()); | 
|  | } finally { | 
|  | try { | 
|  | dos.close(); | 
|  | } catch (IOException e) { | 
|  | // ignore | 
|  | } | 
|  | } | 
|  |  | 
|  | byte[] data = b.toByteArray(); | 
|  | return new Chunk(CHUNK_VURT, data, 0, data.length); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the Theme dump of the provided view. | 
|  | */ | 
|  | private Chunk dumpTheme(View rootView) { | 
|  | ByteArrayOutputStream b = new ByteArrayOutputStream(1024); | 
|  | try { | 
|  | ViewDebug.dumpTheme(rootView, b); | 
|  | } catch (IOException e) { | 
|  | return createFailChunk(1, "Unexpected error while dumping the theme: " | 
|  | + e.getMessage()); | 
|  | } | 
|  |  | 
|  | byte[] data = b.toByteArray(); | 
|  | return new Chunk(CHUNK_VURT, data, 0, data.length); | 
|  | } | 
|  |  | 
|  | private Chunk captureView(View rootView, View targetView) { | 
|  | ByteArrayOutputStream b = new ByteArrayOutputStream(1024); | 
|  | try { | 
|  | ViewDebug.capture(rootView, b, targetView); | 
|  | } catch (IOException e) { | 
|  | return createFailChunk(1, "Unexpected error while capturing view: " | 
|  | + e.getMessage()); | 
|  | } | 
|  |  | 
|  | byte[] data = b.toByteArray(); | 
|  | return new Chunk(CHUNK_VUOP, data, 0, data.length); | 
|  | } | 
|  |  | 
|  | /** Returns the display lists corresponding to the provided view. */ | 
|  | private Chunk dumpDisplayLists(final View rootView, final View targetView) { | 
|  | rootView.post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | ViewDebug.outputDisplayList(rootView, targetView); | 
|  | } | 
|  | }); | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Invokes provided method on the view. | 
|  | * The method name and its arguments are passed in as inputs via the byte buffer. | 
|  | * The buffer contains:<ol> | 
|  | *  <li> len(method name) </li> | 
|  | *  <li> method name </li> | 
|  | *  <li> # of args </li> | 
|  | *  <li> arguments: Each argument comprises of a type specifier followed by the actual argument. | 
|  | *          The type specifier is a single character as used in JNI: | 
|  | *          (Z - boolean, B - byte, C - char, S - short, I - int, J - long, | 
|  | *          F - float, D - double). <p> | 
|  | *          The type specifier is followed by the actual value of argument. | 
|  | *          Booleans are encoded via bytes with 0 indicating false.</li> | 
|  | * </ol> | 
|  | * Methods that take no arguments need only specify the method name. | 
|  | */ | 
|  | private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) { | 
|  | int l = in.getInt(); | 
|  | String methodName = getString(in, l); | 
|  |  | 
|  | Class<?>[] argTypes; | 
|  | Object[] args; | 
|  | if (!in.hasRemaining()) { | 
|  | argTypes = new Class<?>[0]; | 
|  | args = new Object[0]; | 
|  | } else { | 
|  | int nArgs = in.getInt(); | 
|  |  | 
|  | argTypes = new Class<?>[nArgs]; | 
|  | args = new Object[nArgs]; | 
|  |  | 
|  | for (int i = 0; i < nArgs; i++) { | 
|  | char c = in.getChar(); | 
|  | switch (c) { | 
|  | case 'Z': | 
|  | argTypes[i] = boolean.class; | 
|  | args[i] = in.get() == 0 ? false : true; | 
|  | break; | 
|  | case 'B': | 
|  | argTypes[i] = byte.class; | 
|  | args[i] = in.get(); | 
|  | break; | 
|  | case 'C': | 
|  | argTypes[i] = char.class; | 
|  | args[i] = in.getChar(); | 
|  | break; | 
|  | case 'S': | 
|  | argTypes[i] = short.class; | 
|  | args[i] = in.getShort(); | 
|  | break; | 
|  | case 'I': | 
|  | argTypes[i] = int.class; | 
|  | args[i] = in.getInt(); | 
|  | break; | 
|  | case 'J': | 
|  | argTypes[i] = long.class; | 
|  | args[i] = in.getLong(); | 
|  | break; | 
|  | case 'F': | 
|  | argTypes[i] = float.class; | 
|  | args[i] = in.getFloat(); | 
|  | break; | 
|  | case 'D': | 
|  | argTypes[i] = double.class; | 
|  | args[i] = in.getDouble(); | 
|  | break; | 
|  | default: | 
|  | Log.e(TAG, "arg " + i + ", unrecognized type: " + c); | 
|  | return createFailChunk(ERR_INVALID_PARAM, | 
|  | "Unsupported parameter type (" + c + ") to invoke view method."); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | Method method = null; | 
|  | try { | 
|  | method = targetView.getClass().getMethod(methodName, argTypes); | 
|  | } catch (NoSuchMethodException e) { | 
|  | Log.e(TAG, "No such method: " + e.getMessage()); | 
|  | return createFailChunk(ERR_INVALID_PARAM, | 
|  | "No such method: " + e.getMessage()); | 
|  | } | 
|  |  | 
|  | try { | 
|  | ViewDebug.invokeViewMethod(targetView, method, args); | 
|  | } catch (Exception e) { | 
|  | Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage()); | 
|  | String msg = e.getCause().getMessage(); | 
|  | if (msg == null) { | 
|  | msg = e.getCause().toString(); | 
|  | } | 
|  | return createFailChunk(ERR_EXCEPTION, msg); | 
|  | } | 
|  |  | 
|  | return null; | 
|  | } | 
|  |  | 
|  | private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) { | 
|  | int l = in.getInt(); | 
|  | String param = getString(in, l); | 
|  | int value = in.getInt(); | 
|  | try { | 
|  | ViewDebug.setLayoutParameter(targetView, param, value); | 
|  | } catch (Exception e) { | 
|  | Log.e(TAG, "Exception setting layout parameter: " + e); | 
|  | return createFailChunk(ERR_EXCEPTION, "Error accessing field " | 
|  | + param + ":" + e.getMessage()); | 
|  | } | 
|  |  | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /** Profiles provided view. */ | 
|  | private Chunk profileView(View rootView, final View targetView) { | 
|  | ByteArrayOutputStream b = new ByteArrayOutputStream(32 * 1024); | 
|  | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(b), 32 * 1024); | 
|  | try { | 
|  | ViewDebug.profileViewAndChildren(targetView, bw); | 
|  | } catch (IOException e) { | 
|  | return createFailChunk(1, "Unexpected error while profiling view: " + e.getMessage()); | 
|  | } finally { | 
|  | try { | 
|  | bw.close(); | 
|  | } catch (IOException e) { | 
|  | // ignore | 
|  | } | 
|  | } | 
|  |  | 
|  | byte[] data = b.toByteArray(); | 
|  | return new Chunk(CHUNK_VUOP, data, 0, data.length); | 
|  | } | 
|  | } |