diff options
-rw-r--r-- | core/java/android/ddm/DdmHandleGlTracing.java | 60 | ||||
-rw-r--r-- | core/java/android/ddm/DdmHandleHello.java | 19 | ||||
-rw-r--r-- | core/java/android/ddm/DdmHandleViewDebug.java | 315 | ||||
-rw-r--r-- | core/java/android/ddm/DdmRegister.java | 2 | ||||
-rw-r--r-- | core/java/android/view/ViewDebug.java | 55 | ||||
-rw-r--r-- | core/java/android/view/WindowManagerGlobal.java | 23 |
6 files changed, 391 insertions, 83 deletions
diff --git a/core/java/android/ddm/DdmHandleGlTracing.java b/core/java/android/ddm/DdmHandleGlTracing.java deleted file mode 100644 index 511cf44..0000000 --- a/core/java/android/ddm/DdmHandleGlTracing.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2012 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.opengl.GLUtils; - -import org.apache.harmony.dalvik.ddmc.Chunk; -import org.apache.harmony.dalvik.ddmc.ChunkHandler; -import org.apache.harmony.dalvik.ddmc.DdmServer; - -import java.nio.ByteBuffer; - -public class DdmHandleGlTracing extends ChunkHandler { - /** GL TRace control packets. Packet data controls starting/stopping the trace. */ - public static final int CHUNK_GLTR = type("GLTR"); - - private static final DdmHandleGlTracing sInstance = new DdmHandleGlTracing(); - - /** singleton, do not instantiate. */ - private DdmHandleGlTracing() {} - - public static void register() { - DdmServer.registerHandler(CHUNK_GLTR, sInstance); - } - - @Override - public void connected() { - } - - @Override - public void disconnected() { - } - - @Override - public Chunk handleChunk(Chunk request) { - int type = request.type; - - if (type != CHUNK_GLTR) { - throw new RuntimeException("Unknown packet " + ChunkHandler.name(type)); - } - - ByteBuffer in = wrapChunk(request); - GLUtils.setTracingLevel(in.getInt()); - return null; // empty response - } -} diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java index 70ad648..842a482 100644 --- a/core/java/android/ddm/DdmHandleHello.java +++ b/core/java/android/ddm/DdmHandleHello.java @@ -36,7 +36,10 @@ public class DdmHandleHello extends ChunkHandler { private static DdmHandleHello mInstance = new DdmHandleHello(); - private static final String[] NATIVE_FEATURES = new String[] { "opengl-tracing" }; + private static final String[] FRAMEWORK_FEATURES = new String[] { + "opengl-tracing", + "view-hierarchy", + }; /* singleton, do not instantiate */ private DdmHandleHello() {} @@ -155,22 +158,22 @@ public class DdmHandleHello extends ChunkHandler { if (false) Log.v("ddm-heap", "Got feature list request"); - int size = 4 + 4 * (vmFeatures.length + NATIVE_FEATURES.length); + int size = 4 + 4 * (vmFeatures.length + FRAMEWORK_FEATURES.length); for (int i = vmFeatures.length-1; i >= 0; i--) size += vmFeatures[i].length() * 2; - for (int i = NATIVE_FEATURES.length-1; i>= 0; i--) - size += NATIVE_FEATURES[i].length() * 2; + for (int i = FRAMEWORK_FEATURES.length-1; i>= 0; i--) + size += FRAMEWORK_FEATURES[i].length() * 2; ByteBuffer out = ByteBuffer.allocate(size); out.order(ChunkHandler.CHUNK_ORDER); - out.putInt(vmFeatures.length + NATIVE_FEATURES.length); + out.putInt(vmFeatures.length + FRAMEWORK_FEATURES.length); for (int i = vmFeatures.length-1; i >= 0; i--) { out.putInt(vmFeatures[i].length()); putString(out, vmFeatures[i]); } - for (int i = NATIVE_FEATURES.length-1; i >= 0; i--) { - out.putInt(NATIVE_FEATURES[i].length()); - putString(out, NATIVE_FEATURES[i]); + for (int i = FRAMEWORK_FEATURES.length-1; i >= 0; i--) { + out.putInt(FRAMEWORK_FEATURES[i].length()); + putString(out, FRAMEWORK_FEATURES[i]); } return new Chunk(CHUNK_FEAT, out); diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java new file mode 100644 index 0000000..a0578fb --- /dev/null +++ b/core/java/android/ddm/DdmHandleViewDebug.java @@ -0,0 +1,315 @@ +/* + * 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.opengl.GLUtils; +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.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 { + /** Enable/Disable tracing of OpenGL calls. */ + public static final int CHUNK_VUGL = type("VUGL"); + + /** 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; + + /** + * 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; + + /** Invalidate View. */ + private static final int VUOP_INVALIDATE_VIEW = 3; + + /** Re-layout given view. */ + private static final int VUOP_LAYOUT_VIEW = 4; + + /** Profile a view. */ + private static final int VUOP_PROFILE_VIEW = 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; + + private static final DdmHandleViewDebug sInstance = new DdmHandleViewDebug(); + + /** singleton, do not instantiate. */ + private DdmHandleViewDebug() {} + + public static void register() { + DdmServer.registerHandler(CHUNK_VUGL, sInstance); + 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_VUGL) { + return handleOpenGlTrace(request); + } else 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 + 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_INVALIDATE_VIEW: + return invalidateView(rootView, targetView); + case VUOP_LAYOUT_VIEW: + return layoutView(rootView, targetView); + case VUOP_PROFILE_VIEW: + return profileView(rootView, targetView); + default: + return createFailChunk(ERR_INVALID_OP, "Unknown view operation: " + op); + } + } else { + throw new RuntimeException("Unknown packet " + ChunkHandler.name(type)); + } + } + + private Chunk handleOpenGlTrace(Chunk request) { + ByteBuffer in = wrapChunk(request); + GLUtils.setTracingLevel(in.getInt()); + return null; // empty response + } + + /** 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; + + ByteArrayOutputStream b = new ByteArrayOutputStream(1024); + try { + ViewDebug.dump(rootView, skipChildren, includeProperties, b); + } catch (IOException e) { + return createFailChunk(1, "Unexpected error while obtaining view hierarchy: " + + e.getMessage()); + } + + 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); + } + + 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; + } + + /** Invalidates provided view. */ + private Chunk invalidateView(final View rootView, final View targetView) { + targetView.postInvalidate(); + return null; + } + + /** Lays out provided view. */ + private Chunk layoutView(View rootView, final View targetView) { + rootView.post(new Runnable() { + @Override + public void run() { + targetView.requestLayout(); + } + }); + 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); + } +} diff --git a/core/java/android/ddm/DdmRegister.java b/core/java/android/ddm/DdmRegister.java index 2c82967..e0faa51 100644 --- a/core/java/android/ddm/DdmRegister.java +++ b/core/java/android/ddm/DdmRegister.java @@ -51,7 +51,7 @@ public class DdmRegister { DdmHandleNativeHeap.register(); DdmHandleProfiling.register(); DdmHandleExit.register(); - DdmHandleGlTracing.register(); + DdmHandleViewDebug.register(); DdmServer.registrationComplete(); } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 023e58f..6e28268 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -406,7 +406,7 @@ public class ViewDebug { view = view.getRootView(); if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) { - dump(view, clientStream); + dump(view, false, true, clientStream); } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) { captureLayers(view, new DataOutputStream(clientStream)); } else { @@ -425,7 +425,8 @@ public class ViewDebug { } } - private static View findView(View root, String parameter) { + /** @hide */ + public static View findView(View root, String parameter) { // Look by type/hashcode if (parameter.indexOf('@') != -1) { final String[] ids = parameter.split("@"); @@ -488,7 +489,8 @@ public class ViewDebug { } } - private static void profileViewAndChildren(final View view, BufferedWriter out) + /** @hide */ + public static void profileViewAndChildren(final View view, BufferedWriter out) throws IOException { profileViewAndChildren(view, out, true); } @@ -623,7 +625,8 @@ public class ViewDebug { return duration[0]; } - private static void captureLayers(View root, final DataOutputStream clientStream) + /** @hide */ + public static void captureLayers(View root, final DataOutputStream clientStream) throws IOException { try { @@ -695,10 +698,21 @@ public class ViewDebug { view.getViewRootImpl().outputDisplayList(view); } + /** @hide */ + public static void outputDisplayList(View root, View target) { + root.getViewRootImpl().outputDisplayList(target); + } + private static void capture(View root, final OutputStream clientStream, String parameter) throws IOException { final View captureView = findView(root, parameter); + capture(root, clientStream, captureView); + } + + /** @hide */ + public static void capture(View root, final OutputStream clientStream, View captureView) + throws IOException { Bitmap b = performViewCapture(captureView, false); if (b == null) { @@ -752,14 +766,20 @@ public class ViewDebug { return null; } - private static void dump(View root, OutputStream clientStream) throws IOException { + /** + * Dumps the view hierarchy starting from the given view. + * @hide + */ + public static void dump(View root, boolean skipChildren, boolean includeProperties, + OutputStream clientStream) throws IOException { BufferedWriter out = null; try { out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024); View view = root.getRootView(); if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; - dumpViewHierarchyWithProperties(group.getContext(), group, out, 0); + dumpViewHierarchy(group.getContext(), group, out, 0, + skipChildren, includeProperties); } out.write("DONE."); out.newLine(); @@ -804,9 +824,13 @@ public class ViewDebug { return view.getClass().getName().equals(className) && view.hashCode() == hashCode; } - private static void dumpViewHierarchyWithProperties(Context context, ViewGroup group, - BufferedWriter out, int level) { - if (!dumpViewWithProperties(context, group, out, level)) { + private static void dumpViewHierarchy(Context context, ViewGroup group, + BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) { + if (!dumpView(context, group, out, level, includeProperties)) { + return; + } + + if (skipChildren) { return; } @@ -814,9 +838,10 @@ public class ViewDebug { for (int i = 0; i < count; i++) { final View view = group.getChildAt(i); if (view instanceof ViewGroup) { - dumpViewHierarchyWithProperties(context, (ViewGroup) view, out, level + 1); + dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren, + includeProperties); } else { - dumpViewWithProperties(context, view, out, level + 1); + dumpView(context, view, out, level + 1, includeProperties); } } if (group instanceof HierarchyHandler) { @@ -824,8 +849,8 @@ public class ViewDebug { } } - private static boolean dumpViewWithProperties(Context context, View view, - BufferedWriter out, int level) { + private static boolean dumpView(Context context, View view, + BufferedWriter out, int level, boolean includeProperties) { try { for (int i = 0; i < level; i++) { @@ -835,7 +860,9 @@ public class ViewDebug { out.write('@'); out.write(Integer.toHexString(view.hashCode())); out.write(' '); - dumpViewProperties(context, view, out); + if (includeProperties) { + dumpViewProperties(context, view, out); + } out.newLine(); } catch (IOException e) { Log.w("View", "Error while dumping hierarchy tree"); diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index e8945aa..7eb26fa 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -160,6 +160,29 @@ public final class WindowManagerGlobal { } } + public String[] getViewRootNames() { + synchronized (mLock) { + if (mRoots == null) return new String[0]; + String[] mViewRoots = new String[mRoots.length]; + int i = 0; + for (ViewRootImpl root : mRoots) { + mViewRoots[i++] = getWindowName(root); + } + return mViewRoots; + } + } + + public View getRootView(String name) { + synchronized (mLock) { + if (mRoots == null) return null; + for (ViewRootImpl root : mRoots) { + if (name.equals(getWindowName(root))) return root.getView(); + } + } + + return null; + } + public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { if (view == null) { |