Frameworks/base: New preload tool
Add a new preload tool based on hprof dumps. This means that only
a userdebug build (to adjust the pre-existing preloaded-classes
file) is required now, not a recompiled runtime.
Change-Id: Ib0c00de3b248e49fa8271cbace67c5d4a50170a1
diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk
new file mode 100644
index 0000000..35d28fb
--- /dev/null
+++ b/tools/preload2/Android.mk
@@ -0,0 +1,32 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+# To connect to devices (and take hprof dumps).
+LOCAL_STATIC_JAVA_LIBRARIES := ddmlib-prebuilt
+
+# To process hprof dumps.
+LOCAL_STATIC_JAVA_LIBRARIES += perflib-prebuilt trove-prebuilt guavalib
+
+# For JDWP access we use the framework in the JDWP tests from Apache Harmony, for
+# convenience (and to not depend on internal JDK APIs).
+LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit
+
+LOCAL_MODULE:= preload2
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Copy the preload-tool shell script to the host's bin directory.
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := preload-tool
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/preload-tool $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+ $(hide) chmod 755 $@
diff --git a/tools/preload2/preload-tool b/tools/preload2/preload-tool
new file mode 100644
index 0000000..36dbc1c
--- /dev/null
+++ b/tools/preload2/preload-tool
@@ -0,0 +1,37 @@
+# Copyright (C) 2015 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.
+
+# This script is used on the host only. It uses a common subset
+# shell dialect that should work well. It is partially derived
+# from art/tools/art.
+
+function follow_links() {
+ if [ z"$BASH_SOURCE" != z ]; then
+ file="$BASH_SOURCE"
+ else
+ file="$0"
+ fi
+ while [ -h "$file" ]; do
+ # On Mac OS, readlink -f doesn't work.
+ file="$(readlink "$file")"
+ done
+ echo "$file"
+}
+
+
+PROG_NAME="$(follow_links)"
+PROG_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
+ANDROID_ROOT=$PROG_DIR/..
+
+java -cp $ANDROID_ROOT/framework/preload2.jar com.android.preload.Main
diff --git a/tools/preload2/src/com/android/preload/ClientUtils.java b/tools/preload2/src/com/android/preload/ClientUtils.java
new file mode 100644
index 0000000..71ef025
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/ClientUtils.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+
+/**
+ * Helper class for common communication with a Client (the ddms name for a running application).
+ *
+ * Instances take a default timeout parameter that's applied to all functions without explicit
+ * timeout. Timeouts are in milliseconds.
+ */
+public class ClientUtils {
+
+ private int defaultTimeout;
+
+ public ClientUtils() {
+ this(10000);
+ }
+
+ public ClientUtils(int defaultTimeout) {
+ this.defaultTimeout = defaultTimeout;
+ }
+
+ /**
+ * Shortcut for findClient with default timeout.
+ */
+ public Client findClient(IDevice device, String processName, int processPid) {
+ return findClient(device, processName, processPid, defaultTimeout);
+ }
+
+ /**
+ * Find the client with the given process name or process id. The name takes precedence over
+ * the process id (if valid). Stop looking after the given timeout.
+ *
+ * @param device The device to communicate with.
+ * @param processName The name of the process. May be null.
+ * @param processPid The pid of the process. Values less than or equal to zero are ignored.
+ * @param timeout The amount of milliseconds to wait, at most.
+ * @return The client, if found. Otherwise null.
+ */
+ public Client findClient(IDevice device, String processName, int processPid, int timeout) {
+ WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout);
+ return wfc.get();
+ }
+
+ /**
+ * Shortcut for findAllClients with default timeout.
+ */
+ public Client[] findAllClients(IDevice device) {
+ return findAllClients(device, defaultTimeout);
+ }
+
+ /**
+ * Retrieve all clients known to the given device. Wait at most the given timeout.
+ *
+ * @param device The device to investigate.
+ * @param timeout The amount of milliseconds to wait, at most.
+ * @return An array of clients running on the given device. May be null depending on the
+ * device implementation.
+ */
+ public Client[] findAllClients(IDevice device, int timeout) {
+ if (device.hasClients()) {
+ return device.getClients();
+ }
+ WaitForClients wfc = new WaitForClients(device, timeout);
+ return wfc.get();
+ }
+
+ private static class WaitForClient implements IClientChangeListener {
+
+ private IDevice device;
+ private String processName;
+ private int processPid;
+ private long timeout;
+ private Client result;
+
+ public WaitForClient(IDevice device, String processName, int processPid, long timeout) {
+ this.device = device;
+ this.processName = processName;
+ this.processPid = processPid;
+ this.timeout = timeout;
+ this.result = null;
+ }
+
+ public Client get() {
+ synchronized (this) {
+ AndroidDebugBridge.addClientChangeListener(this);
+
+ // Maybe it's already there.
+ if (result == null) {
+ result = searchForClient(device);
+ }
+
+ if (result == null) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ // Note: doesn't guard for spurious wakeup.
+ }
+ }
+ }
+
+ AndroidDebugBridge.removeClientChangeListener(this);
+ return result;
+ }
+
+ private Client searchForClient(IDevice device) {
+ if (processName != null) {
+ Client tmp = device.getClient(processName);
+ if (tmp != null) {
+ return tmp;
+ }
+ }
+ if (processPid > 0) {
+ String name = device.getClientName(processPid);
+ if (name != null && !name.isEmpty()) {
+ Client tmp = device.getClient(name);
+ if (tmp != null) {
+ return tmp;
+ }
+ }
+ }
+ if (processPid > 0) {
+ // Try manual search.
+ for (Client cl : device.getClients()) {
+ if (cl.getClientData().getPid() == processPid
+ && cl.getClientData().getClientDescription() != null) {
+ return cl;
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean isTargetClient(Client c) {
+ if (processPid > 0 && c.getClientData().getPid() == processPid) {
+ return true;
+ }
+ if (processName != null
+ && processName.equals(c.getClientData().getClientDescription())) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void clientChanged(Client arg0, int arg1) {
+ synchronized (this) {
+ if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
+ if (isTargetClient(arg0)) {
+ result = arg0;
+ notifyAll();
+ }
+ }
+ }
+ }
+ }
+
+ private static class WaitForClients implements IClientChangeListener {
+
+ private IDevice device;
+ private long timeout;
+
+ public WaitForClients(IDevice device, long timeout) {
+ this.device = device;
+ this.timeout = timeout;
+ }
+
+ public Client[] get() {
+ synchronized (this) {
+ AndroidDebugBridge.addClientChangeListener(this);
+
+ if (device.hasClients()) {
+ return device.getClients();
+ }
+
+ try {
+ wait(timeout); // Note: doesn't guard for spurious wakeup.
+ } catch (InterruptedException exc) {
+ }
+
+ // We will be woken up when the first client data arrives. Sleep a little longer
+ // to give (hopefully all of) the rest of the clients a chance to become available.
+ // Note: a loop with timeout is brittle as well and complicated, just accept this
+ // for now.
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException exc) {
+ }
+ }
+
+ AndroidDebugBridge.removeClientChangeListener(this);
+
+ return device.getClients();
+ }
+
+ @Override
+ public void clientChanged(Client arg0, int arg1) {
+ synchronized (this) {
+ if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
+ notifyAll();
+ }
+ }
+ }
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/DeviceUtils.java b/tools/preload2/src/com/android/preload/DeviceUtils.java
new file mode 100644
index 0000000..72de7b5
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DeviceUtils.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
+import com.android.preload.classdataretrieval.hprof.Hprof;
+import com.android.ddmlib.DdmPreferences;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+
+import java.util.Date;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class for some device routines.
+ */
+public class DeviceUtils {
+
+ public static void init(int debugPort) {
+ DdmPreferences.setSelectedDebugPort(debugPort);
+
+ Hprof.init();
+
+ AndroidDebugBridge.init(true);
+
+ AndroidDebugBridge.createBridge();
+ }
+
+ /**
+ * Run a command in the shell on the device.
+ */
+ public static void doShell(IDevice device, String cmdline, long timeout, TimeUnit unit) {
+ doShell(device, cmdline, new NullShellOutputReceiver(), timeout, unit);
+ }
+
+ /**
+ * Run a command in the shell on the device. Collects and returns the console output.
+ */
+ public static String doShellReturnString(IDevice device, String cmdline, long timeout,
+ TimeUnit unit) {
+ CollectStringShellOutputReceiver rec = new CollectStringShellOutputReceiver();
+ doShell(device, cmdline, rec, timeout, unit);
+ return rec.toString();
+ }
+
+ /**
+ * Run a command in the shell on the device, directing all output to the given receiver.
+ */
+ public static void doShell(IDevice device, String cmdline, IShellOutputReceiver receiver,
+ long timeout, TimeUnit unit) {
+ try {
+ device.executeShellCommand(cmdline, receiver, timeout, unit);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Run am start on the device.
+ */
+ public static void doAMStart(IDevice device, String name, String activity) {
+ doShell(device, "am start -n " + name + " /." + activity, 30, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Find the device with the given serial. Give up after the given timeout (in milliseconds).
+ */
+ public static IDevice findDevice(String serial, int timeout) {
+ WaitForDevice wfd = new WaitForDevice(serial, timeout);
+ return wfd.get();
+ }
+
+ /**
+ * Get all devices ddms knows about. Wait at most for the given timeout.
+ */
+ public static IDevice[] findDevices(int timeout) {
+ WaitForDevice wfd = new WaitForDevice(null, timeout);
+ wfd.get();
+ return AndroidDebugBridge.getBridge().getDevices();
+ }
+
+ /**
+ * Return the build type of the given device. This is the value of the "ro.build.type"
+ * system property.
+ */
+ public static String getBuildType(IDevice device) {
+ try {
+ Future<String> buildType = device.getSystemProperty("ro.build.type");
+ return buildType.get(500, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ }
+ return null;
+ }
+
+ /**
+ * Check whether the given device has a pre-optimized boot image. More precisely, checks
+ * whether /system/framework/ * /boot.art exists.
+ */
+ public static boolean hasPrebuiltBootImage(IDevice device) {
+ String ret =
+ doShellReturnString(device, "ls /system/framework/*/boot.art", 500, TimeUnit.MILLISECONDS);
+
+ return !ret.contains("No such file or directory");
+ }
+
+ /**
+ * Remove files involved in a standard build that interfere with collecting data. This will
+ * remove /etc/preloaded-classes, which determines which classes are allocated already in the
+ * boot image. It also deletes any compiled boot image on the device. Then it restarts the
+ * device.
+ *
+ * This is a potentially long-running operation, as the boot after the deletion may take a while.
+ * The method will abort after the given timeout.
+ */
+ public static boolean removePreloaded(IDevice device, long preloadedWaitTimeInSeconds) {
+ String oldContent =
+ DeviceUtils.doShellReturnString(device, "cat /etc/preloaded-classes", 1, TimeUnit.SECONDS);
+ if (oldContent.trim().equals("")) {
+ System.out.println("Preloaded-classes already empty.");
+ return true;
+ }
+
+ // Stop the system server etc.
+ doShell(device, "stop", 100, TimeUnit.MILLISECONDS);
+
+ // Remount /system, delete /etc/preloaded-classes. It would be nice to use "adb remount,"
+ // but AndroidDebugBridge doesn't expose it.
+ doShell(device, "mount -o remount,rw /system", 500, TimeUnit.MILLISECONDS);
+ doShell(device, "rm /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
+ // We do need an empty file.
+ doShell(device, "touch /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
+
+ // Delete the files in the dalvik cache.
+ doShell(device, "rm /data/dalvik-cache/*/*boot.art", 500, TimeUnit.MILLISECONDS);
+
+ // We'll try to use dev.bootcomplete to know when the system server is back up. But stop
+ // doesn't reset it, so do it manually.
+ doShell(device, "setprop dev.bootcomplete \"0\"", 500, TimeUnit.MILLISECONDS);
+
+ // Start the system server.
+ doShell(device, "start", 100, TimeUnit.MILLISECONDS);
+
+ // Do a loop checking each second whether bootcomplete. Wait for at most the given
+ // threshold.
+ Date startDate = new Date();
+ for (;;) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // Ignore spurious wakeup.
+ }
+ // Check whether bootcomplete.
+ String ret =
+ doShellReturnString(device, "getprop dev.bootcomplete", 500, TimeUnit.MILLISECONDS);
+ if (ret.trim().equals("1")) {
+ break;
+ }
+ System.out.println("Still not booted: " + ret);
+
+ // Check whether we timed out. This is a simplistic check that doesn't take into account
+ // things like switches in time.
+ Date endDate = new Date();
+ long seconds =
+ TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS);
+ if (seconds > preloadedWaitTimeInSeconds) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Enable method-tracing on device. The system should be restarted after this.
+ */
+ public static void enableTracing(IDevice device) {
+ // Disable selinux.
+ doShell(device, "setenforce 0", 100, TimeUnit.MILLISECONDS);
+
+ // Make the profile directory world-writable.
+ doShell(device, "chmod 777 /data/dalvik-cache/profiles", 100, TimeUnit.MILLISECONDS);
+
+ // Enable streaming method tracing with a small 1K buffer.
+ doShell(device, "setprop dalvik.vm.method-trace true", 100, TimeUnit.MILLISECONDS);
+ doShell(device, "setprop dalvik.vm.method-trace-file "
+ + "/data/dalvik-cache/profiles/zygote.trace.bin", 100, TimeUnit.MILLISECONDS);
+ doShell(device, "setprop dalvik.vm.method-trace-file-siz 1024", 100, TimeUnit.MILLISECONDS);
+ doShell(device, "setprop dalvik.vm.method-trace-stream true", 100, TimeUnit.MILLISECONDS);
+ }
+
+ private static class NullShellOutputReceiver implements IShellOutputReceiver {
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public void addOutput(byte[] arg0, int arg1, int arg2) {}
+ }
+
+ private static class CollectStringShellOutputReceiver implements IShellOutputReceiver {
+
+ private StringBuilder builder = new StringBuilder();
+
+ @Override
+ public String toString() {
+ String ret = builder.toString();
+ // Strip trailing newlines. They are especially ugly because adb uses DOS line endings.
+ while (ret.endsWith("\r") || ret.endsWith("\n")) {
+ ret = ret.substring(0, ret.length() - 1);
+ }
+ return ret;
+ }
+
+ @Override
+ public void addOutput(byte[] arg0, int arg1, int arg2) {
+ builder.append(new String(arg0, arg1, arg2));
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ }
+
+ private static class WaitForDevice {
+
+ private String serial;
+ private long timeout;
+ private IDevice device;
+
+ public WaitForDevice(String serial, long timeout) {
+ this.serial = serial;
+ this.timeout = timeout;
+ device = null;
+ }
+
+ public IDevice get() {
+ if (device == null) {
+ WaitForDeviceListener wfdl = new WaitForDeviceListener(serial);
+ synchronized (wfdl) {
+ AndroidDebugBridge.addDeviceChangeListener(wfdl);
+
+ // Check whether we already know about this device.
+ IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
+ if (serial != null) {
+ for (IDevice d : devices) {
+ if (serial.equals(d.getSerialNumber())) {
+ // Only accept if there are clients already. Else wait for the callback informing
+ // us that we now have clients.
+ if (d.hasClients()) {
+ device = d;
+ }
+
+ break;
+ }
+ }
+ } else {
+ if (devices.length > 0) {
+ device = devices[0];
+ }
+ }
+
+ if (device == null) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ // Ignore spurious wakeups.
+ }
+ device = wfdl.getDevice();
+ }
+
+ AndroidDebugBridge.removeDeviceChangeListener(wfdl);
+ }
+ }
+
+ if (device != null) {
+ // Wait for clients.
+ WaitForClientsListener wfcl = new WaitForClientsListener(device);
+ synchronized (wfcl) {
+ AndroidDebugBridge.addDeviceChangeListener(wfcl);
+
+ if (!device.hasClients()) {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ // Ignore spurious wakeups.
+ }
+ }
+
+ AndroidDebugBridge.removeDeviceChangeListener(wfcl);
+ }
+ }
+
+ return device;
+ }
+
+ private static class WaitForDeviceListener implements IDeviceChangeListener {
+
+ private String serial;
+ private IDevice device;
+
+ public WaitForDeviceListener(String serial) {
+ this.serial = serial;
+ }
+
+ public IDevice getDevice() {
+ return device;
+ }
+
+ @Override
+ public void deviceChanged(IDevice arg0, int arg1) {
+ // We may get a device changed instead of connected. Handle like a connection.
+ deviceConnected(arg0);
+ }
+
+ @Override
+ public void deviceConnected(IDevice arg0) {
+ if (device != null) {
+ // Ignore updates.
+ return;
+ }
+
+ if (serial == null || serial.equals(arg0.getSerialNumber())) {
+ device = arg0;
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void deviceDisconnected(IDevice arg0) {
+ // Ignore disconnects.
+ }
+
+ }
+
+ private static class WaitForClientsListener implements IDeviceChangeListener {
+
+ private IDevice myDevice;
+
+ public WaitForClientsListener(IDevice myDevice) {
+ this.myDevice = myDevice;
+ }
+
+ @Override
+ public void deviceChanged(IDevice arg0, int arg1) {
+ if (arg0 == myDevice && (arg1 & IDevice.CHANGE_CLIENT_LIST) != 0) {
+ // Got a client list, done here.
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void deviceConnected(IDevice arg0) {
+ }
+
+ @Override
+ public void deviceDisconnected(IDevice arg0) {
+ }
+
+ }
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/DumpData.java b/tools/preload2/src/com/android/preload/DumpData.java
new file mode 100644
index 0000000..d997224
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DumpData.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Holds the collected data for a process.
+ */
+public class DumpData {
+ /**
+ * Name of the package (=application).
+ */
+ String packageName;
+
+ /**
+ * A map of class name to a string for the classloader. This may be a toString equivalent,
+ * or just a unique ID.
+ */
+ Map<String, String> dumpData;
+
+ /**
+ * The Date when this data was captured. Mostly for display purposes.
+ */
+ Date date;
+
+ /**
+ * A cached value for the number of boot classpath classes (classloader value in dumpData is
+ * null).
+ */
+ int bcpClasses;
+
+ public DumpData(String packageName, Map<String, String> dumpData, Date date) {
+ this.packageName = packageName;
+ this.dumpData = dumpData;
+ this.date = date;
+
+ countBootClassPath();
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public Map<String, String> getDumpData() {
+ return dumpData;
+ }
+
+ public void countBootClassPath() {
+ bcpClasses = 0;
+ for (Map.Entry<String, String> e : dumpData.entrySet()) {
+ if (e.getValue() == null) {
+ bcpClasses++;
+ }
+ }
+ }
+
+ // Return an inverted mapping.
+ public Map<String, Set<String>> invertData() {
+ Map<String, Set<String>> ret = new HashMap<>();
+ for (Map.Entry<String, String> e : dumpData.entrySet()) {
+ if (!ret.containsKey(e.getValue())) {
+ ret.put(e.getValue(), new HashSet<String>());
+ }
+ ret.get(e.getValue()).add(e.getKey());
+ }
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/DumpDataIO.java b/tools/preload2/src/com/android/preload/DumpDataIO.java
new file mode 100644
index 0000000..28625c5
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DumpDataIO.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.File;
+import java.io.FileReader;
+import java.text.DateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Helper class for serialization and deserialization of a collection of DumpData objects to XML.
+ */
+public class DumpDataIO {
+
+ /**
+ * Serialize the given collection to an XML document. Returns the produced string.
+ */
+ public static String serialize(Collection<DumpData> data) {
+ // We'll do this by hand, constructing a DOM or similar is too complicated for our simple
+ // use case.
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("<preloaded-classes-data>\n");
+
+ for (DumpData d : data) {
+ serialize(d, sb);
+ }
+
+ sb.append("</preloaded-classes-data>\n");
+ return sb.toString();
+ }
+
+ private static void serialize(DumpData d, StringBuilder sb) {
+ sb.append("<data package=\"" + d.packageName + "\" date=\"" +
+ DateFormat.getDateTimeInstance().format(d.date) +"\">\n");
+
+ for (Map.Entry<String, String> e : d.dumpData.entrySet()) {
+ sb.append("<class name=\"" + e.getKey() + "\" classloader=\"" + e.getValue() + "\"/>\n");
+ }
+
+ sb.append("</data>\n");
+ }
+
+ /**
+ * Load a collection of DumpData objects from the given file.
+ */
+ public static Collection<DumpData> deserialize(File f) throws Exception {
+ // Use SAX parsing. Our format is very simple. Don't do any schema validation or such.
+
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ spf.setNamespaceAware(false);
+ SAXParser saxParser = spf.newSAXParser();
+
+ XMLReader xmlReader = saxParser.getXMLReader();
+ DumpDataContentHandler ddch = new DumpDataContentHandler();
+ xmlReader.setContentHandler(ddch);
+ xmlReader.parse(new InputSource(new FileReader(f)));
+
+ return ddch.data;
+ }
+
+ private static class DumpDataContentHandler extends DefaultHandler {
+ Collection<DumpData> data = new LinkedList<DumpData>();
+ DumpData openData = null;
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ if (qName.equals("data")) {
+ if (openData != null) {
+ throw new IllegalStateException();
+ }
+ String pkg = attributes.getValue("package");
+ String dateString = attributes.getValue("date");
+
+ if (pkg == null || dateString == null) {
+ throw new IllegalArgumentException();
+ }
+
+ try {
+ Date date = DateFormat.getDateTimeInstance().parse(dateString);
+ openData = new DumpData(pkg, new HashMap<String, String>(), date);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ } else if (qName.equals("class")) {
+ if (openData == null) {
+ throw new IllegalStateException();
+ }
+ String className = attributes.getValue("name");
+ String classLoader = attributes.getValue("classloader");
+
+ if (className == null || classLoader == null) {
+ throw new IllegalArgumentException();
+ }
+
+ openData.dumpData.put(className, classLoader.equals("null") ? null : classLoader);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (qName.equals("data")) {
+ if (openData == null) {
+ throw new IllegalStateException();
+ }
+ openData.countBootClassPath();
+
+ data.add(openData);
+ openData = null;
+ }
+ }
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/DumpTableModel.java b/tools/preload2/src/com/android/preload/DumpTableModel.java
new file mode 100644
index 0000000..d97cbf0
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/DumpTableModel.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * A table model for collected DumpData. This is both the internal storage as well as the model
+ * for display.
+ */
+public class DumpTableModel extends AbstractTableModel {
+
+ private List<DumpData> data = new ArrayList<DumpData>();
+
+ public void addData(DumpData d) {
+ data.add(d);
+ fireTableRowsInserted(data.size() - 1, data.size() - 1);
+ }
+
+ public void clear() {
+ int size = data.size();
+ if (size > 0) {
+ data.clear();
+ fireTableRowsDeleted(0, size - 1);
+ }
+ }
+
+ public List<DumpData> getData() {
+ return data;
+ }
+
+ @Override
+ public int getRowCount() {
+ return data.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 4;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ switch (column) {
+ case 0:
+ return "Package";
+ case 1:
+ return "Date";
+ case 2:
+ return "# All Classes";
+ case 3:
+ return "# Boot Classpath Classes";
+
+ default:
+ throw new IndexOutOfBoundsException(String.valueOf(column));
+ }
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ DumpData d = data.get(rowIndex);
+ switch (columnIndex) {
+ case 0:
+ return d.packageName;
+ case 1:
+ return d.date;
+ case 2:
+ return d.dumpData.size();
+ case 3:
+ return d.bcpClasses;
+
+ default:
+ throw new IndexOutOfBoundsException(String.valueOf(columnIndex));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java
new file mode 100644
index 0000000..ca5b0e0
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/Main.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.actions.ClearTableAction;
+import com.android.preload.actions.ComputeThresholdAction;
+import com.android.preload.actions.ComputeThresholdXAction;
+import com.android.preload.actions.DeviceSpecific;
+import com.android.preload.actions.ExportAction;
+import com.android.preload.actions.ImportAction;
+import com.android.preload.actions.ReloadListAction;
+import com.android.preload.actions.RunMonkeyAction;
+import com.android.preload.actions.ScanAllPackagesAction;
+import com.android.preload.actions.ScanPackageAction;
+import com.android.preload.actions.ShowDataAction;
+import com.android.preload.classdataretrieval.ClassDataRetriever;
+import com.android.preload.classdataretrieval.hprof.Hprof;
+import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever;
+import com.android.preload.ui.UI;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.Action;
+import javax.swing.DefaultListModel;
+
+public class Main {
+
+ /**
+ * Enable tracing mode. This is a work-in-progress to derive compiled-methods data, so it is
+ * off for now.
+ */
+ public final static boolean ENABLE_TRACING = false;
+
+ /**
+ * Ten-second timeout.
+ */
+ public final static int DEFAULT_TIMEOUT_MILLIS = 10 * 1000;
+
+ /**
+ * Hprof timeout. Two minutes.
+ */
+ public final static int HPROF_TIMEOUT_MILLIS = 120 * 1000;
+
+ private IDevice device;
+ private static ClientUtils clientUtils;
+
+ private DumpTableModel dataTableModel;
+ private DefaultListModel<Client> clientListModel;
+
+ private UI ui;
+
+ // Actions that need to be updated once a device is selected.
+ private Collection<DeviceSpecific> deviceSpecificActions;
+
+ // Current main instance.
+ private static Main top;
+ private static boolean useJdwpClassDataRetriever = false;
+
+ public final static String CLASS_PRELOAD_BLACKLIST = "android.app.AlarmManager$" + "|"
+ + "android.app.SearchManager$" + "|" + "android.os.FileObserver$" + "|"
+ + "com.android.server.PackageManagerService\\$AppDirObserver$" + "|" +
+
+
+ // Threads
+ "android.os.AsyncTask$" + "|" + "android.pim.ContactsAsyncHelper$" + "|"
+ + "android.webkit.WebViewClassic\\$1$" + "|" + "java.lang.ProcessManager$" + "|"
+ + "(.*\\$NoPreloadHolder$)";
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ Main m = new Main();
+ top = m;
+
+ m.startUp();
+ }
+
+ public Main() {
+ clientListModel = new DefaultListModel<Client>();
+ dataTableModel = new DumpTableModel();
+
+ clientUtils = new ClientUtils(DEFAULT_TIMEOUT_MILLIS); // Client utils with 10s timeout.
+
+ List<Action> actions = new ArrayList<Action>();
+ actions.add(new ReloadListAction(clientUtils, null, clientListModel));
+ actions.add(new ClearTableAction(dataTableModel));
+ actions.add(new RunMonkeyAction(null, dataTableModel));
+ actions.add(new ScanPackageAction(clientUtils, null, dataTableModel));
+ actions.add(new ScanAllPackagesAction(clientUtils, null, dataTableModel));
+ actions.add(new ComputeThresholdAction("Compute preloaded-classes", dataTableModel, 2,
+ CLASS_PRELOAD_BLACKLIST));
+ actions.add(new ComputeThresholdAction("Compute compiled-classes", dataTableModel, 1,
+ null));
+ actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel,
+ CLASS_PRELOAD_BLACKLIST));
+ actions.add(new ShowDataAction(dataTableModel));
+ actions.add(new ImportAction(dataTableModel));
+ actions.add(new ExportAction(dataTableModel));
+
+ deviceSpecificActions = new ArrayList<DeviceSpecific>();
+ for (Action a : actions) {
+ if (a instanceof DeviceSpecific) {
+ deviceSpecificActions.add((DeviceSpecific)a);
+ }
+ }
+
+ ui = new UI(clientListModel, dataTableModel, actions);
+ ui.setVisible(true);
+ }
+
+ public static UI getUI() {
+ return top.ui;
+ }
+
+ public static ClassDataRetriever getClassDataRetriever() {
+ if (useJdwpClassDataRetriever) {
+ return new JDWPClassDataRetriever();
+ } else {
+ return new Hprof(HPROF_TIMEOUT_MILLIS);
+ }
+ }
+
+ public IDevice getDevice() {
+ return device;
+ }
+
+ public void setDevice(IDevice device) {
+ this.device = device;
+ for (DeviceSpecific ds : deviceSpecificActions) {
+ ds.setDevice(device);
+ }
+ }
+
+ public DefaultListModel<Client> getClientListModel() {
+ return clientListModel;
+ }
+
+ static class DeviceWrapper {
+ IDevice device;
+
+ public DeviceWrapper(IDevice d) {
+ device = d;
+ }
+
+ @Override
+ public String toString() {
+ return device.getName() + " (#" + device.getSerialNumber() + ")";
+ }
+ }
+
+ private void startUp() {
+ getUI().showWaitDialog();
+ initDevice();
+
+ // Load clients.
+ new ReloadListAction(clientUtils, getDevice(), clientListModel).run();
+
+ getUI().hideWaitDialog();
+ }
+
+ private void initDevice() {
+ DeviceUtils.init(DEFAULT_TIMEOUT_MILLIS);
+
+ IDevice devices[] = DeviceUtils.findDevices(DEFAULT_TIMEOUT_MILLIS);
+ if (devices == null || devices.length == 0) {
+ throw new RuntimeException("Could not find any devices...");
+ }
+
+ getUI().hideWaitDialog();
+
+ DeviceWrapper deviceWrappers[] = new DeviceWrapper[devices.length];
+ for (int i = 0; i < devices.length; i++) {
+ deviceWrappers[i] = new DeviceWrapper(devices[i]);
+ }
+
+ DeviceWrapper ret = Main.getUI().showChoiceDialog("Choose a device", "Choose device",
+ deviceWrappers);
+ if (ret != null) {
+ setDevice(ret.device);
+ } else {
+ System.exit(0);
+ }
+
+ boolean prepare = Main.getUI().showConfirmDialog("Prepare device?",
+ "Do you want to prepare the device? This is highly recommended.");
+ if (prepare) {
+ String buildType = DeviceUtils.getBuildType(device);
+ if (buildType == null || (!buildType.equals("userdebug") && !buildType.equals("eng"))) {
+ Main.getUI().showMessageDialog("Need a userdebug or eng build! (Found " + buildType
+ + ")");
+ return;
+ }
+ if (DeviceUtils.hasPrebuiltBootImage(device)) {
+ Main.getUI().showMessageDialog("Cannot prepare a device with pre-optimized boot "
+ + "image!");
+ return;
+ }
+
+ if (ENABLE_TRACING) {
+ DeviceUtils.enableTracing(device);
+ }
+
+ Main.getUI().showMessageDialog("The device will reboot. This will potentially take a "
+ + "long time. Please be patient.");
+ if (!DeviceUtils.removePreloaded(device, 15 * 60) /* 15m timeout */) {
+ Main.getUI().showMessageDialog("Removing preloaded-classes failed unexpectedly!");
+ }
+ }
+ }
+
+ public static Map<String, String> findAndGetClassData(IDevice device, String packageName)
+ throws Exception {
+ Client client = clientUtils.findClient(device, packageName, -1);
+ if (client == null) {
+ throw new RuntimeException("Could not find client...");
+ }
+ System.out.println("Found client: " + client);
+
+ return getClassDataRetriever().getClassData(client);
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
new file mode 100644
index 0000000..fbf83d2
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+public abstract class AbstractThreadedAction extends AbstractAction implements Runnable {
+
+ protected AbstractThreadedAction(String title) {
+ super(title);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ new Thread(this).start();
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java
new file mode 100644
index 0000000..7906417
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.ddmlib.IDevice;
+
+import java.awt.event.ActionEvent;
+
+public abstract class AbstractThreadedDeviceSpecificAction extends AbstractThreadedAction
+ implements DeviceSpecific {
+
+ protected IDevice device;
+
+ protected AbstractThreadedDeviceSpecificAction(String title, IDevice device) {
+ super(title);
+ this.device = device;
+ }
+
+ @Override
+ public void setDevice(IDevice device) {
+ this.device = device;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (device == null) {
+ return;
+ }
+ super.actionPerformed(e);
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/actions/ClearTableAction.java b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java
new file mode 100644
index 0000000..c0e4795
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.preload.DumpTableModel;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+public class ClearTableAction extends AbstractAction {
+ private final DumpTableModel dataTableModel;
+
+ public ClearTableAction(DumpTableModel dataTableModel) {
+ super("Clear");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dataTableModel.clear();
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
new file mode 100644
index 0000000..b524716
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+
+/**
+ * Compute an intersection of classes from the given data. A class is in the intersection if it
+ * appears in at least the number of threshold given packages. An optional blacklist can be
+ * used to filter classes from the intersection.
+ */
+public class ComputeThresholdAction extends AbstractAction implements Runnable {
+ protected int threshold;
+ private Pattern blacklist;
+ private DumpTableModel dataTableModel;
+
+ /**
+ * Create an action with the given parameters. The blacklist is a regular expression
+ * that filters classes.
+ */
+ public ComputeThresholdAction(String name, DumpTableModel dataTableModel, int threshold,
+ String blacklist) {
+ super(name);
+ this.dataTableModel = dataTableModel;
+ this.threshold = threshold;
+ if (blacklist != null) {
+ this.blacklist = Pattern.compile(blacklist);
+ }
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ List<DumpData> data = dataTableModel.getData();
+ if (data.size() == 0) {
+ Main.getUI().showMessageDialog("No data available, please scan packages or run "
+ + "monkeys.");
+ return;
+ }
+ if (data.size() == 1) {
+ Main.getUI().showMessageDialog("Cannot compute list from only one data set, please "
+ + "scan packages or run monkeys.");
+ return;
+ }
+
+ new Thread(this).start();
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ Map<String, Set<String>> uses = new HashMap<String, Set<String>>();
+ for (DumpData d : dataTableModel.getData()) {
+ Main.getUI().updateWaitDialog("Merging " + d.getPackageName());
+ updateClassUse(d.getPackageName(), uses, getBootClassPathClasses(d.getDumpData()));
+ }
+
+ Main.getUI().updateWaitDialog("Computing thresholded set");
+ Set<String> result = fromThreshold(uses, blacklist, threshold);
+ Main.getUI().hideWaitDialog();
+
+ boolean ret = Main.getUI().showConfirmDialog("Computed a set with " + result.size()
+ + " classes, would you like to save to disk?", "Save?");
+ if (ret) {
+ JFileChooser jfc = new JFileChooser();
+ int ret2 = jfc.showSaveDialog(Main.getUI());
+ if (ret2 == JFileChooser.APPROVE_OPTION) {
+ File f = jfc.getSelectedFile();
+ saveSet(result, f);
+ }
+ }
+ }
+
+ private Set<String> fromThreshold(Map<String, Set<String>> classUses, Pattern blacklist,
+ int threshold) {
+ TreeSet<String> ret = new TreeSet<>(); // TreeSet so it's nicely ordered by name.
+
+ for (Map.Entry<String, Set<String>> e : classUses.entrySet()) {
+ if (e.getValue().size() >= threshold) {
+ if (blacklist == null || !blacklist.matcher(e.getKey()).matches()) {
+ ret.add(e.getKey());
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ private static void updateClassUse(String pkg, Map<String, Set<String>> classUses,
+ Set<String> classes) {
+ for (String className : classes) {
+ Set<String> old = classUses.get(className);
+ if (old == null) {
+ classUses.put(className, new HashSet<String>());
+ }
+ classUses.get(className).add(pkg);
+ }
+ }
+
+ private static Set<String> getBootClassPathClasses(Map<String, String> source) {
+ Set<String> ret = new HashSet<>();
+ for (Map.Entry<String, String> e : source.entrySet()) {
+ if (e.getValue() == null) {
+ ret.add(e.getKey());
+ }
+ }
+ return ret;
+ }
+
+ private static void saveSet(Set<String> result, File f) {
+ try {
+ PrintWriter out = new PrintWriter(f);
+ for (String s : result) {
+ out.println(s);
+ }
+ out.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java
new file mode 100644
index 0000000..3ec0a4c
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+public class ComputeThresholdXAction extends ComputeThresholdAction {
+
+ public ComputeThresholdXAction(String name, DumpTableModel dataTableModel,
+ String blacklist) {
+ super(name, dataTableModel, 1, blacklist);
+ }
+
+ @Override
+ public void run() {
+ String value = Main.getUI().showInputDialog("Threshold?");
+
+ if (value != null) {
+ try {
+ threshold = Integer.parseInt(value);
+ super.run();
+ } catch (Exception exc) {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java
new file mode 100644
index 0000000..35a8f26
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.ddmlib.IDevice;
+
+/**
+ * Marks an action as being device-specific. The user must set the device through the specified
+ * method if the device selection changes.
+ *
+ * Implementors must tolerate a null device (for example, with a no-op). This includes calling
+ * any methods before setDevice has been called.
+ */
+public interface DeviceSpecific {
+
+ /**
+ * Set the device that should be used. Note that there is no restriction on calling other
+ * methods of the implementor before a setDevice call. Neither is device guaranteed to be
+ * non-null.
+ *
+ * @param device The device to use going forward.
+ */
+ public void setDevice(IDevice device);
+}
diff --git a/tools/preload2/src/com/android/preload/actions/ExportAction.java b/tools/preload2/src/com/android/preload/actions/ExportAction.java
new file mode 100644
index 0000000..cb8b3df
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ExportAction.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.preload.DumpDataIO;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.PrintWriter;
+
+import javax.swing.AbstractAction;
+
+public class ExportAction extends AbstractAction implements Runnable {
+ private File lastSaveFile;
+ private DumpTableModel dataTableModel;
+
+ public ExportAction(DumpTableModel dataTableModel) {
+ super("Export data");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ lastSaveFile = Main.getUI().showSaveDialog();
+ if (lastSaveFile != null) {
+ new Thread(this).start();
+ }
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ String serialized = DumpDataIO.serialize(dataTableModel.getData());
+
+ if (serialized != null) {
+ try {
+ PrintWriter out = new PrintWriter(lastSaveFile);
+ out.println(serialized);
+ out.close();
+
+ Main.getUI().hideWaitDialog();
+ } catch (Exception e) {
+ Main.getUI().hideWaitDialog();
+ Main.getUI().showMessageDialog("Failed writing: " + e.getMessage());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ImportAction.java b/tools/preload2/src/com/android/preload/actions/ImportAction.java
new file mode 100644
index 0000000..5c19765
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ImportAction.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.preload.DumpData;
+import com.android.preload.DumpDataIO;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.Collection;
+
+import javax.swing.AbstractAction;
+
+public class ImportAction extends AbstractAction implements Runnable {
+ private File[] lastOpenFiles;
+ private DumpTableModel dataTableModel;
+
+ public ImportAction(DumpTableModel dataTableModel) {
+ super("Import data");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ lastOpenFiles = Main.getUI().showOpenDialog(true);
+ if (lastOpenFiles != null) {
+ new Thread(this).start();
+ }
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ for (File f : lastOpenFiles) {
+ try {
+ Collection<DumpData> data = DumpDataIO.deserialize(f);
+
+ for (DumpData d : data) {
+ dataTableModel.addData(d);
+ }
+ } catch (Exception e) {
+ Main.getUI().showMessageDialog("Failed reading: " + e.getMessage());
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ReloadListAction.java b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java
new file mode 100644
index 0000000..29f0557
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.ClientUtils;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import javax.swing.DefaultListModel;
+
+public class ReloadListAction extends AbstractThreadedDeviceSpecificAction {
+
+ private ClientUtils clientUtils;
+ private final DefaultListModel<Client> clientListModel;
+
+ public ReloadListAction(ClientUtils utils, IDevice device,
+ DefaultListModel<Client> clientListModel) {
+ super("Reload", device);
+ this.clientUtils = utils;
+ this.clientListModel = clientListModel;
+ }
+
+ @Override
+ public void run() {
+ Client[] clients = clientUtils.findAllClients(device);
+ if (clients != null) {
+ Arrays.sort(clients, new ClientComparator());
+ }
+ clientListModel.removeAllElements();
+ for (Client c : clients) {
+ clientListModel.addElement(c);
+ }
+ }
+
+ private static class ClientComparator implements Comparator<Client> {
+
+ @Override
+ public int compare(Client o1, Client o2) {
+ String s1 = o1.getClientData().getClientDescription();
+ String s2 = o2.getClientData().getClientDescription();
+
+ if (s1 == null || s2 == null) {
+ // Not good, didn't get all data?
+ return (s1 == null) ? -1 : 1;
+ }
+
+ return s1.compareTo(s2);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
new file mode 100644
index 0000000..385e857
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.ddmlib.IDevice;
+import com.android.preload.DeviceUtils;
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.AbstractAction;
+
+public class RunMonkeyAction extends AbstractAction implements DeviceSpecific {
+
+ private final static String DEFAULT_MONKEY_PACKAGES =
+ "com.android.calendar,com.android.gallery3d";
+
+ private IDevice device;
+ private DumpTableModel dataTableModel;
+
+ public RunMonkeyAction(IDevice device, DumpTableModel dataTableModel) {
+ super("Run monkey");
+ this.device = device;
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void setDevice(IDevice device) {
+ this.device = device;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String packages = Main.getUI().showInputDialog("Please enter packages name to run with"
+ + " the monkey, or leave empty for default.");
+ if (packages == null) {
+ return;
+ }
+ if (packages.isEmpty()) {
+ packages = DEFAULT_MONKEY_PACKAGES;
+ }
+ new Thread(new RunMonkeyRunnable(packages)).start();
+ }
+
+ private class RunMonkeyRunnable implements Runnable {
+
+ private String packages;
+ private final static int ITERATIONS = 1000;
+
+ public RunMonkeyRunnable(String packages) {
+ this.packages = packages;
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ String pkgs[] = packages.split(",");
+
+ for (String pkg : pkgs) {
+ Main.getUI().updateWaitDialog("Running monkey on " + pkg);
+
+ try {
+ // Stop running app.
+ forceStop(pkg);
+
+ // Little bit of breather here.
+ try {
+ Thread.sleep(1000);
+ } catch (Exception e) {
+ }
+
+ DeviceUtils.doShell(device, "monkey -p " + pkg + " " + ITERATIONS, 1,
+ TimeUnit.MINUTES);
+
+ Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
+ Map<String, String> data = Main.findAndGetClassData(device, pkg);
+ DumpData dumpData = new DumpData(pkg, data, new Date());
+ dataTableModel.addData(dumpData);
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ // Stop running app.
+ forceStop(pkg);
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+ }
+
+ private void forceStop(String packageName) {
+ // Stop running app.
+ DeviceUtils.doShell(device, "force-stop " + packageName, 5, TimeUnit.SECONDS);
+ DeviceUtils.doShell(device, "kill " + packageName, 5, TimeUnit.SECONDS);
+ DeviceUtils.doShell(device, "kill `pid " + packageName + "`", 5, TimeUnit.SECONDS);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java
new file mode 100644
index 0000000..d74b8a3
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.ClientUtils;
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.util.Date;
+import java.util.Map;
+
+public class ScanAllPackagesAction extends AbstractThreadedDeviceSpecificAction {
+
+ private ClientUtils clientUtils;
+ private DumpTableModel dataTableModel;
+
+ public ScanAllPackagesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
+ super("Scan all packages", device);
+ this.clientUtils = utils;
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ Client[] clients = clientUtils.findAllClients(device);
+ for (Client c : clients) {
+ String pkg = c.getClientData().getClientDescription();
+ Main.getUI().showWaitDialog();
+ Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
+
+ try {
+ Map<String, String> data = Main.getClassDataRetriever().getClassData(c);
+ DumpData dumpData = new DumpData(pkg, data, new Date());
+ dataTableModel.addData(dumpData);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java
new file mode 100644
index 0000000..98492bd
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.ClientUtils;
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.util.Date;
+import java.util.Map;
+
+public class ScanPackageAction extends AbstractThreadedDeviceSpecificAction {
+
+ private ClientUtils clientUtils;
+ private DumpTableModel dataTableModel;
+
+ public ScanPackageAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
+ super("Scan package", device);
+ this.clientUtils = utils;
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+
+ try {
+ Client client = Main.getUI().getSelectedClient();
+ if (client != null) {
+ work(client);
+ } else {
+ Client[] clients = clientUtils.findAllClients(device);
+ if (clients.length > 0) {
+ ClientWrapper[] clientWrappers = new ClientWrapper[clients.length];
+ for (int i = 0; i < clientWrappers.length; i++) {
+ clientWrappers[i] = new ClientWrapper(clients[i]);
+ }
+ Main.getUI().hideWaitDialog();
+
+ ClientWrapper ret = Main.getUI().showChoiceDialog("Choose a package to scan",
+ "Choose package",
+ clientWrappers);
+ if (ret != null) {
+ work(ret.client);
+ }
+ }
+ }
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+ }
+
+ private void work(Client c) {
+ String pkg = c.getClientData().getClientDescription();
+ Main.getUI().showWaitDialog();
+ Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
+
+ try {
+ Map<String, String> data = Main.findAndGetClassData(device, pkg);
+ DumpData dumpData = new DumpData(pkg, data, new Date());
+ dataTableModel.addData(dumpData);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static class ClientWrapper {
+ private Client client;
+
+ public ClientWrapper(Client c) {
+ client = c;
+ }
+
+ @Override
+ public String toString() {
+ return client.getClientData().getClientDescription() + " (pid "
+ + client.getClientData().getPid() + ")";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ShowDataAction.java b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java
new file mode 100644
index 0000000..2bb175f
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.actions;
+
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+public class ShowDataAction extends AbstractAction {
+ private DumpTableModel dataTableModel;
+
+ public ShowDataAction(DumpTableModel dataTableModel) {
+ super("Show data");
+ this.dataTableModel = dataTableModel;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // TODO(agampe): Auto-generated method stub
+ int selRow = Main.getUI().getSelectedDataTableRow();
+ if (selRow != -1) {
+ DumpData data = dataTableModel.getData().get(selRow);
+ Map<String, Set<String>> inv = data.invertData();
+
+ StringBuilder builder = new StringBuilder();
+
+ // First bootclasspath.
+ add(builder, "Boot classpath:", inv.get(null));
+
+ // Now everything else.
+ for (String k : inv.keySet()) {
+ if (k != null) {
+ builder.append("==================\n\n");
+ add(builder, k, inv.get(k));
+ }
+ }
+
+ JFrame newFrame = new JFrame(data.getPackageName() + " " + data.getDate());
+ newFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+ newFrame.getContentPane().add(new JScrollPane(new JTextArea(builder.toString())),
+ BorderLayout.CENTER);
+ newFrame.setSize(800, 600);
+ newFrame.setLocationRelativeTo(null);
+ newFrame.setVisible(true);
+ }
+ }
+
+ private void add(StringBuilder builder, String head, Set<String> set) {
+ builder.append(head);
+ builder.append('\n');
+ addSet(builder, set);
+ builder.append('\n');
+ }
+
+ private void addSet(StringBuilder builder, Set<String> set) {
+ if (set == null) {
+ builder.append(" NONE\n");
+ return;
+ }
+ List<String> sorted = new ArrayList<>(set);
+ Collections.sort(sorted);
+ for (String s : sorted) {
+ builder.append(s);
+ builder.append('\n');
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java
new file mode 100644
index 0000000..f04360f
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.classdataretrieval;
+
+import com.android.ddmlib.Client;
+
+import java.util.Map;
+
+/**
+ * Retrieve a class-to-classloader map for loaded classes from the client.
+ */
+public interface ClassDataRetriever {
+
+ public Map<String, String> getClassData(Client client);
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java
new file mode 100644
index 0000000..8d797ee
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.classdataretrieval.hprof;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.ClientData.IHprofDumpHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GeneralHprofDumpHandler implements IHprofDumpHandler {
+
+ private List<IHprofDumpHandler> handlers = new ArrayList<>();
+
+ public void addHandler(IHprofDumpHandler h) {
+ synchronized (handlers) {
+ handlers.add(h);
+ }
+ }
+
+ public void removeHandler(IHprofDumpHandler h) {
+ synchronized (handlers) {
+ handlers.remove(h);
+ }
+ }
+
+ private List<IHprofDumpHandler> getIterationList() {
+ synchronized (handlers) {
+ return new ArrayList<>(handlers);
+ }
+ }
+
+ @Override
+ public void onEndFailure(Client arg0, String arg1) {
+ List<IHprofDumpHandler> iterList = getIterationList();
+ for (IHprofDumpHandler h : iterList) {
+ h.onEndFailure(arg0, arg1);
+ }
+ }
+
+ @Override
+ public void onSuccess(String arg0, Client arg1) {
+ List<IHprofDumpHandler> iterList = getIterationList();
+ for (IHprofDumpHandler h : iterList) {
+ h.onSuccess(arg0, arg1);
+ }
+ }
+
+ @Override
+ public void onSuccess(byte[] arg0, Client arg1) {
+ List<IHprofDumpHandler> iterList = getIterationList();
+ for (IHprofDumpHandler h : iterList) {
+ h.onSuccess(arg0, arg1);
+ }
+ }
+ }
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
new file mode 100644
index 0000000..21b7a04
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.classdataretrieval.hprof;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.ClientData;
+import com.android.ddmlib.ClientData.IHprofDumpHandler;
+import com.android.preload.classdataretrieval.ClassDataRetriever;
+import com.android.preload.ui.NullProgressMonitor;
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Queries;
+import com.android.tools.perflib.heap.Snapshot;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class Hprof implements ClassDataRetriever {
+
+ private static GeneralHprofDumpHandler hprofHandler;
+
+ public static void init() {
+ synchronized(Hprof.class) {
+ if (hprofHandler == null) {
+ ClientData.setHprofDumpHandler(hprofHandler = new GeneralHprofDumpHandler());
+ }
+ }
+ }
+
+ public static File doHprof(Client client, int timeout) {
+ GetHprof gh = new GetHprof(client, timeout);
+ return gh.get();
+ }
+
+ /**
+ * Return a map of class names to class-loader names derived from the hprof dump.
+ *
+ * @param hprofLocalFile
+ */
+ public static Map<String, String> analyzeHprof(File hprofLocalFile) throws Exception {
+ Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprofLocalFile));
+
+ Map<String, Set<ClassObj>> classes = Queries.classes(snapshot, null);
+ Map<String, String> retValue = new HashMap<String, String>();
+ for (Map.Entry<String, Set<ClassObj>> e : classes.entrySet()) {
+ for (ClassObj c : e.getValue()) {
+ String cl = c.getClassLoader() == null ? null : c.getClassLoader().toString();
+ String cName = c.getClassName();
+ int aDepth = 0;
+ while (cName.endsWith("[]")) {
+ cName = cName.substring(0, cName.length()-2);
+ aDepth++;
+ }
+ String newName = transformPrimitiveClass(cName);
+ if (aDepth > 0) {
+ // Need to use kind-a descriptor syntax. If it was transformed, it is primitive.
+ if (newName.equals(cName)) {
+ newName = "L" + newName + ";";
+ }
+ for (int i = 0; i < aDepth; i++) {
+ newName = "[" + newName;
+ }
+ }
+ retValue.put(newName, cl);
+ }
+ }
+
+ // Free up memory.
+ snapshot.dispose();
+
+ return retValue;
+ }
+
+ private static Map<String, String> primitiveMapping;
+
+ static {
+ primitiveMapping = new HashMap<>();
+ primitiveMapping.put("boolean", "Z");
+ primitiveMapping.put("byte", "B");
+ primitiveMapping.put("char", "C");
+ primitiveMapping.put("double", "D");
+ primitiveMapping.put("float", "F");
+ primitiveMapping.put("int", "I");
+ primitiveMapping.put("long", "J");
+ primitiveMapping.put("short", "S");
+ primitiveMapping.put("void", "V");
+ }
+
+ private static String transformPrimitiveClass(String name) {
+ String rep = primitiveMapping.get(name);
+ if (rep != null) {
+ return rep;
+ }
+ return name;
+ }
+
+ private static class GetHprof implements IHprofDumpHandler {
+
+ private File target;
+ private long timeout;
+ private Client client;
+
+ public GetHprof(Client client, long timeout) {
+ this.client = client;
+ this.timeout = timeout;
+ }
+
+ public File get() {
+ synchronized (this) {
+ hprofHandler.addHandler(this);
+ client.dumpHprof();
+ if (target == null) {
+ try {
+ wait(timeout);
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ }
+ }
+
+ hprofHandler.removeHandler(this);
+ return target;
+ }
+
+ private void wakeUp() {
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void onEndFailure(Client arg0, String arg1) {
+ System.out.println("GetHprof.onEndFailure");
+ if (client == arg0) {
+ wakeUp();
+ }
+ }
+
+ private static File createTargetFile() {
+ try {
+ return File.createTempFile("ddms", ".hprof");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void onSuccess(String arg0, Client arg1) {
+ System.out.println("GetHprof.onSuccess");
+ if (client == arg1) {
+ try {
+ target = createTargetFile();
+ arg1.getDevice().getSyncService().pullFile(arg0,
+ target.getAbsoluteFile().toString(), new NullProgressMonitor());
+ } catch (Exception e) {
+ e.printStackTrace();
+ target = null;
+ }
+ wakeUp();
+ }
+ }
+
+ @Override
+ public void onSuccess(byte[] arg0, Client arg1) {
+ System.out.println("GetHprof.onSuccess");
+ if (client == arg1) {
+ try {
+ target = createTargetFile();
+ BufferedOutputStream out =
+ new BufferedOutputStream(new FileOutputStream(target));
+ out.write(arg0);
+ out.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ target = null;
+ }
+ wakeUp();
+ }
+ }
+ }
+
+ private int timeout;
+
+ public Hprof(int timeout) {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public Map<String, String> getClassData(Client client) {
+ File hprofLocalFile = Hprof.doHprof(client, timeout);
+ if (hprofLocalFile == null) {
+ throw new RuntimeException("Failed getting dump...");
+ }
+ System.out.println("Dump file is " + hprofLocalFile);
+
+ try {
+ return analyzeHprof(hprofLocalFile);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java
new file mode 100644
index 0000000..dbd4c89
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.classdataretrieval.jdwp;
+
+import com.android.ddmlib.Client;
+import com.android.preload.classdataretrieval.ClassDataRetriever;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
+import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPTestCase;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPUnitDebuggeeWrapper;
+import org.apache.harmony.jpda.tests.share.JPDALogWriter;
+import org.apache.harmony.jpda.tests.share.JPDATestOptions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class JDWPClassDataRetriever extends JDWPTestCase implements ClassDataRetriever {
+
+ private final Client client;
+
+ public JDWPClassDataRetriever() {
+ this(null);
+ }
+
+ public JDWPClassDataRetriever(Client client) {
+ this.client = client;
+ }
+
+
+ @Override
+ protected String getDebuggeeClassName() {
+ return "<unset>";
+ }
+
+ @Override
+ public Map<String, String> getClassData(Client client) {
+ return new JDWPClassDataRetriever(client).retrieve();
+ }
+
+ private Map<String, String> retrieve() {
+ if (client == null) {
+ throw new IllegalStateException();
+ }
+
+ settings = createTestOptions("localhost:" + String.valueOf(client.getDebuggerListenPort()));
+ settings.setDebuggeeSuspend("n");
+
+ logWriter = new JPDALogWriter(System.out, "", false);
+
+ try {
+ internalSetUp();
+
+ return retrieveImpl();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ internalTearDown();
+ }
+ }
+
+ private Map<String, String> retrieveImpl() {
+ try {
+ // Suspend the app.
+ {
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.VirtualMachineCommandSet.CommandSetID,
+ JDWPCommands.VirtualMachineCommandSet.SuspendCommand);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return null;
+ }
+ }
+
+ // List all classes.
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.VirtualMachineCommandSet.CommandSetID,
+ JDWPCommands.VirtualMachineCommandSet.AllClassesCommand);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return null;
+ }
+
+ int classCount = reply.getNextValueAsInt();
+ System.out.println("Runtime reported " + classCount + " classes.");
+
+ Map<Long, String> classes = new HashMap<Long, String>();
+ Map<Long, String> arrayClasses = new HashMap<Long, String>();
+
+ for (int i = 0; i < classCount; i++) {
+ byte refTypeTag = reply.getNextValueAsByte();
+ long typeID = reply.getNextValueAsReferenceTypeID();
+ String signature = reply.getNextValueAsString();
+ /* int status = */ reply.getNextValueAsInt();
+
+ switch (refTypeTag) {
+ case JDWPConstants.TypeTag.CLASS:
+ case JDWPConstants.TypeTag.INTERFACE:
+ classes.put(typeID, signature);
+ break;
+
+ case JDWPConstants.TypeTag.ARRAY:
+ arrayClasses.put(typeID, signature);
+ break;
+ }
+ }
+
+ Map<String, String> result = new HashMap<String, String>();
+
+ // Parse all classes.
+ for (Map.Entry<Long, String> entry : classes.entrySet()) {
+ long typeID = entry.getKey();
+ String signature = entry.getValue();
+
+ if (!checkClass(typeID, signature, result)) {
+ System.err.println("Issue investigating " + signature);
+ }
+ }
+
+ // For arrays, look at the leaf component type.
+ for (Map.Entry<Long, String> entry : arrayClasses.entrySet()) {
+ long typeID = entry.getKey();
+ String signature = entry.getValue();
+
+ if (!checkArrayClass(typeID, signature, result)) {
+ System.err.println("Issue investigating " + signature);
+ }
+ }
+
+ return result;
+ } finally {
+ // Resume the app.
+ {
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.VirtualMachineCommandSet.CommandSetID,
+ JDWPCommands.VirtualMachineCommandSet.ResumeCommand);
+ /* ReplyPacket reply = */ debuggeeWrapper.vmMirror.performCommand(packet);
+ }
+ }
+ }
+
+ private boolean checkClass(long typeID, String signature, Map<String, String> result) {
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
+ JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
+ packet.setNextValueAsReferenceTypeID(typeID);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return false;
+ }
+
+ long classLoaderID = reply.getNextValueAsObjectID();
+
+ // TODO: Investigate the classloader to have a better string?
+ String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
+
+ result.put(getClassName(signature), classLoaderString);
+
+ return true;
+ }
+
+ private boolean checkArrayClass(long typeID, String signature, Map<String, String> result) {
+ // Classloaders of array classes are the same as the component class'.
+ CommandPacket packet = new CommandPacket(
+ JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
+ JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
+ packet.setNextValueAsReferenceTypeID(typeID);
+ ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
+ if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
+ return false;
+ }
+
+ long classLoaderID = reply.getNextValueAsObjectID();
+
+ // TODO: Investigate the classloader to have a better string?
+ String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
+
+ // For array classes, we *need* the signature directly.
+ result.put(signature, classLoaderString);
+
+ return true;
+ }
+
+ private static String getClassName(String signature) {
+ String withoutLAndSemicolon = signature.substring(1, signature.length() - 1);
+ return withoutLAndSemicolon.replace('/', '.');
+ }
+
+
+ private static JPDATestOptions createTestOptions(String address) {
+ JPDATestOptions options = new JPDATestOptions();
+ options.setAttachConnectorKind();
+ options.setTimeout(1000);
+ options.setWaitingTime(1000);
+ options.setTransportAddress(address);
+ return options;
+ }
+
+ @Override
+ protected JDWPUnitDebuggeeWrapper createDebuggeeWrapper() {
+ return new PreloadDebugeeWrapper(settings, logWriter);
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java
new file mode 100644
index 0000000..b9df6d0
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.classdataretrieval.jdwp;
+
+import org.apache.harmony.jpda.tests.framework.LogWriter;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPManualDebuggeeWrapper;
+import org.apache.harmony.jpda.tests.share.JPDATestOptions;
+
+import java.io.IOException;
+
+public class PreloadDebugeeWrapper extends JDWPManualDebuggeeWrapper {
+
+ public PreloadDebugeeWrapper(JPDATestOptions options, LogWriter writer) {
+ super(options, writer);
+ }
+
+ @Override
+ protected Process launchProcess(String cmdLine) throws IOException {
+ return null;
+ }
+
+ @Override
+ protected void WaitForProcessExit(Process process) {
+ }
+
+}
diff --git a/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java
new file mode 100644
index 0000000..f45aad0
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.ui;
+
+import com.android.ddmlib.SyncService.ISyncProgressMonitor;
+
+public class NullProgressMonitor implements ISyncProgressMonitor {
+
+ @Override
+ public void advance(int arg0) {}
+
+ @Override
+ public boolean isCanceled() {
+ return false;
+ }
+
+ @Override
+ public void start(int arg0) {}
+
+ @Override
+ public void startSubTask(String arg0) {}
+
+ @Override
+ public void stop() {}
+}
\ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/ui/UI.java b/tools/preload2/src/com/android/preload/ui/UI.java
new file mode 100644
index 0000000..47174dd
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/ui/UI.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2015 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 com.android.preload.ui;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.ClientData;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.io.File;
+import java.util.List;
+
+import javax.swing.Action;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JToolBar;
+import javax.swing.ListModel;
+import javax.swing.SwingUtilities;
+import javax.swing.table.TableModel;
+
+public class UI extends JFrame {
+
+ private JList<Client> clientList;
+ private JTable dataTable;
+
+ // Shared file chooser, means the directory is retained.
+ private JFileChooser jfc;
+
+ public UI(ListModel<Client> clientListModel,
+ TableModel dataTableModel,
+ List<Action> actions) {
+ super("Preloaded-classes computation");
+
+ getContentPane().add(new JScrollPane(clientList = new JList<Client>(clientListModel)),
+ BorderLayout.WEST);
+ clientList.setCellRenderer(new ClientListCellRenderer());
+ // clientList.addListSelectionListener(listener);
+
+ dataTable = new JTable(dataTableModel);
+ getContentPane().add(new JScrollPane(dataTable), BorderLayout.CENTER);
+
+ JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL);
+ for (Action a : actions) {
+ if (a == null) {
+ toolbar.addSeparator();
+ } else {
+ toolbar.add(a);
+ }
+ }
+ getContentPane().add(toolbar, BorderLayout.PAGE_START);
+
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ setBounds(100, 100, 800, 600);
+ }
+
+ public Client getSelectedClient() {
+ return clientList.getSelectedValue();
+ }
+
+ public int getSelectedDataTableRow() {
+ return dataTable.getSelectedRow();
+ }
+
+ private JDialog currentWaitDialog = null;
+
+ public void showWaitDialog() {
+ if (currentWaitDialog == null) {
+ currentWaitDialog = new JDialog(this, "Please wait...", true);
+ currentWaitDialog.getContentPane().add(new JLabel("Please be patient."),
+ BorderLayout.CENTER);
+ JProgressBar progress = new JProgressBar(JProgressBar.HORIZONTAL);
+ progress.setIndeterminate(true);
+ currentWaitDialog.getContentPane().add(progress, BorderLayout.SOUTH);
+ currentWaitDialog.setSize(200, 100);
+ currentWaitDialog.setLocationRelativeTo(null);
+ showWaitDialogLater();
+ }
+ }
+
+ private void showWaitDialogLater() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(true); // This is blocking.
+ }
+ }
+ });
+ }
+
+ public void updateWaitDialog(String s) {
+ if (currentWaitDialog != null) {
+ ((JLabel) currentWaitDialog.getContentPane().getComponent(0)).setText(s);
+ Dimension prefSize = currentWaitDialog.getPreferredSize();
+ Dimension curSize = currentWaitDialog.getSize();
+ if (prefSize.width > curSize.width || prefSize.height > curSize.height) {
+ currentWaitDialog.setSize(Math.max(prefSize.width, curSize.width),
+ Math.max(prefSize.height, curSize.height));
+ currentWaitDialog.invalidate();
+ }
+ }
+ }
+
+ public void hideWaitDialog() {
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ currentWaitDialog = null;
+ }
+ }
+
+ public void showMessageDialog(String s) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try {
+ JOptionPane.showMessageDialog(this, s);
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public boolean showConfirmDialog(String title, String message) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try {
+ return JOptionPane.showConfirmDialog(this, title, message, JOptionPane.YES_NO_OPTION)
+ == JOptionPane.YES_OPTION;
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public String showInputDialog(String message) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try {
+ return JOptionPane.showInputDialog(message);
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T showChoiceDialog(String title, String message, T[] choices) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try{
+ return (T)JOptionPane.showInputDialog(this,
+ title,
+ message,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ choices,
+ choices[0]);
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public File showSaveDialog() {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try{
+ if (jfc == null) {
+ jfc = new JFileChooser();
+ }
+
+ int ret = jfc.showSaveDialog(this);
+ if (ret == JFileChooser.APPROVE_OPTION) {
+ return jfc.getSelectedFile();
+ } else {
+ return null;
+ }
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ public File[] showOpenDialog(boolean multi) {
+ // Hide the wait dialog...
+ if (currentWaitDialog != null) {
+ currentWaitDialog.setVisible(false);
+ }
+
+ try{
+ if (jfc == null) {
+ jfc = new JFileChooser();
+ }
+
+ jfc.setMultiSelectionEnabled(multi);
+ int ret = jfc.showOpenDialog(this);
+ if (ret == JFileChooser.APPROVE_OPTION) {
+ return jfc.getSelectedFiles();
+ } else {
+ return null;
+ }
+ } finally {
+ // And reshow it afterwards...
+ if (currentWaitDialog != null) {
+ showWaitDialogLater();
+ }
+ }
+ }
+
+ private class ClientListCellRenderer extends DefaultListCellRenderer {
+
+ @Override
+ public Component getListCellRendererComponent(JList<?> list, Object value, int index,
+ boolean isSelected, boolean cellHasFocus) {
+ ClientData cd = ((Client) value).getClientData();
+ String s = cd.getClientDescription() + " (pid " + cd.getPid() + ")";
+ return super.getListCellRendererComponent(list, s, index, isSelected, cellHasFocus);
+ }
+ }
+}