Merge "Remove preload2 as it is not used"
am: c9f969486c
Change-Id: Ib6021811cc9833d98fca15e6dc99b7d9b0dcd0a6
This commit is contained in:
@@ -1,50 +0,0 @@
|
||||
// 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.
|
||||
|
||||
java_library_host {
|
||||
name: "preload2",
|
||||
|
||||
srcs: ["src/**/*.java"],
|
||||
|
||||
// To connect to devices (and take hprof dumps).
|
||||
static_libs: [
|
||||
"ddmlib-prebuilt",
|
||||
"tools-common-prebuilt",
|
||||
|
||||
// To process hprof dumps.
|
||||
"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).
|
||||
"apache-harmony-jdwp-tests",
|
||||
"junit",
|
||||
],
|
||||
|
||||
// Copy to build artifacts
|
||||
dist: {
|
||||
targets: [
|
||||
"dist_files",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
// Copy the preload-tool shell script to the host's bin directory.
|
||||
sh_binary_host {
|
||||
name: "preload-tool",
|
||||
src: "preload-tool",
|
||||
required: ["preload2"],
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
# 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 $@
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,420 +0,0 @@
|
||||
/*
|
||||
* 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.AdbCommandRejectedException;
|
||||
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 com.android.ddmlib.SyncException;
|
||||
import com.android.ddmlib.TimeoutException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Helper class for some device routines.
|
||||
*/
|
||||
public class DeviceUtils {
|
||||
|
||||
// Locations
|
||||
private static final String PRELOADED_CLASSES_FILE = "/etc/preloaded-classes";
|
||||
// Shell commands
|
||||
private static final String CREATE_EMPTY_PRELOADED_CMD = "touch " + PRELOADED_CLASSES_FILE;
|
||||
private static final String DELETE_CACHE_CMD = "rm /data/dalvik-cache/*/*boot.art";
|
||||
private static final String DELETE_PRELOADED_CMD = "rm " + PRELOADED_CLASSES_FILE;
|
||||
private static final String READ_PRELOADED_CMD = "cat " + PRELOADED_CLASSES_FILE;
|
||||
private static final String START_SHELL_CMD = "start";
|
||||
private static final String STOP_SHELL_CMD = "stop";
|
||||
private static final String REMOUNT_SYSTEM_CMD = "mount -o rw,remount /system";
|
||||
private static final String UNSET_BOOTCOMPLETE_CMD = "setprop dev.bootcomplete \"0\"";
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* Write over the preloaded-classes file with an empty or existing file and regenerate the boot
|
||||
* image as necessary.
|
||||
*
|
||||
* @param device
|
||||
* @param pcFile
|
||||
* @param bootTimeout
|
||||
* @throws AdbCommandRejectedException
|
||||
* @throws IOException
|
||||
* @throws TimeoutException
|
||||
* @throws SyncException
|
||||
* @return true if successfully overwritten, false otherwise
|
||||
*/
|
||||
public static boolean overwritePreloaded(IDevice device, File pcFile, long bootTimeout)
|
||||
throws AdbCommandRejectedException, IOException, TimeoutException, SyncException {
|
||||
boolean writeEmpty = (pcFile == null);
|
||||
if (writeEmpty) {
|
||||
// Check if the preloaded-classes file is already empty.
|
||||
String oldContent =
|
||||
doShellReturnString(device, READ_PRELOADED_CMD, 1, TimeUnit.SECONDS);
|
||||
if (oldContent.trim().equals("")) {
|
||||
System.out.println("Preloaded-classes already empty.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the system server etc.
|
||||
doShell(device, STOP_SHELL_CMD, 1, TimeUnit.SECONDS);
|
||||
// Remount the read-only system partition
|
||||
doShell(device, REMOUNT_SYSTEM_CMD, 1, TimeUnit.SECONDS);
|
||||
// Delete the preloaded-classes file
|
||||
doShell(device, DELETE_PRELOADED_CMD, 1, TimeUnit.SECONDS);
|
||||
// Delete the dalvik cache files
|
||||
doShell(device, DELETE_CACHE_CMD, 1, TimeUnit.SECONDS);
|
||||
if (writeEmpty) {
|
||||
// Write an empty preloaded-classes file
|
||||
doShell(device, CREATE_EMPTY_PRELOADED_CMD, 500, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
// Push the new preloaded-classes file
|
||||
device.pushFile(pcFile.getAbsolutePath(), PRELOADED_CLASSES_FILE);
|
||||
}
|
||||
// Manually reset the boot complete flag
|
||||
doShell(device, UNSET_BOOTCOMPLETE_CMD, 1, TimeUnit.SECONDS);
|
||||
// Restart system server on the device
|
||||
doShell(device, START_SHELL_CMD, 1, TimeUnit.SECONDS);
|
||||
// Wait for the boot complete flag and return the outcome.
|
||||
return waitForBootComplete(device, bootTimeout);
|
||||
}
|
||||
|
||||
private static boolean waitForBootComplete(IDevice device, long timeout) {
|
||||
// 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 > timeout) {
|
||||
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 {
|
||||
wfdl.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 {
|
||||
wfcl.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) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
/*
|
||||
* 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.actions.WritePreloadedClassesAction;
|
||||
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.IUI;
|
||||
import com.android.preload.ui.SequenceUI;
|
||||
import com.android.preload.ui.SwingUI;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
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 IUI 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$)";
|
||||
|
||||
public final static String SCAN_ALL_CMD = "scan-all";
|
||||
public final static String SCAN_PACKAGE_CMD = "scan";
|
||||
public final static String COMPUTE_FILE_CMD = "comp";
|
||||
public final static String EXPORT_CMD = "export";
|
||||
public final static String IMPORT_CMD = "import";
|
||||
public final static String WRITE_CMD = "write";
|
||||
|
||||
/**
|
||||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
Main m;
|
||||
if (args.length > 0 && args[0].equals("--seq")) {
|
||||
m = createSequencedMain(args);
|
||||
} else {
|
||||
m = new Main(new SwingUI());
|
||||
}
|
||||
|
||||
top = m;
|
||||
m.startUp();
|
||||
}
|
||||
|
||||
public Main(IUI ui) {
|
||||
this.ui = ui;
|
||||
|
||||
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 WritePreloadedClassesAction(clientUtils, null, dataTableModel));
|
||||
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.prepare(clientListModel, dataTableModel, actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args
|
||||
* @return
|
||||
*/
|
||||
private static Main createSequencedMain(String[] args) {
|
||||
SequenceUI ui = new SequenceUI();
|
||||
Main main = new Main(ui);
|
||||
|
||||
Iterator<String> it = Arrays.asList(args).iterator();
|
||||
it.next(); // --seq
|
||||
// Setup
|
||||
ui.choice("#" + it.next()); // Device.
|
||||
ui.confirmNo(); // Prepare: no.
|
||||
// Actions
|
||||
try {
|
||||
while (it.hasNext()) {
|
||||
String op = it.next();
|
||||
// Operation: Scan a single package
|
||||
if (SCAN_PACKAGE_CMD.equals(op)) {
|
||||
System.out.println("Scanning package.");
|
||||
ui.action(ScanPackageAction.class);
|
||||
ui.client(it.next());
|
||||
// Operation: Scan all packages
|
||||
} else if (SCAN_ALL_CMD.equals(op)) {
|
||||
System.out.println("Scanning all packages.");
|
||||
ui.action(ScanAllPackagesAction.class);
|
||||
// Operation: Export the output to a file
|
||||
} else if (EXPORT_CMD.equals(op)) {
|
||||
System.out.println("Exporting data.");
|
||||
ui.action(ExportAction.class);
|
||||
ui.output(new File(it.next()));
|
||||
// Operation: Import the input from a file or directory
|
||||
} else if (IMPORT_CMD.equals(op)) {
|
||||
System.out.println("Importing data.");
|
||||
File file = new File(it.next());
|
||||
if (!file.exists()) {
|
||||
throw new RuntimeException(
|
||||
String.format("File does not exist, %s.", file.getAbsolutePath()));
|
||||
} else if (file.isFile()) {
|
||||
ui.action(ImportAction.class);
|
||||
ui.input(file);
|
||||
} else if (file.isDirectory()) {
|
||||
for (File content : file.listFiles()) {
|
||||
ui.action(ImportAction.class);
|
||||
ui.input(content);
|
||||
}
|
||||
}
|
||||
// Operation: Compute preloaded classes with specific threshold
|
||||
} else if (COMPUTE_FILE_CMD.equals(op)) {
|
||||
System.out.println("Compute preloaded classes.");
|
||||
ui.action(ComputeThresholdXAction.class);
|
||||
ui.input(it.next());
|
||||
ui.confirmYes();
|
||||
ui.output(new File(it.next()));
|
||||
// Operation: Write preloaded classes from a specific file
|
||||
} else if (WRITE_CMD.equals(op)) {
|
||||
System.out.println("Writing preloaded classes.");
|
||||
ui.action(WritePreloadedClassesAction.class);
|
||||
ui.input(new File(it.next()));
|
||||
}
|
||||
}
|
||||
} catch (NoSuchElementException e) {
|
||||
System.out.println("Failed to parse action sequence correctly.");
|
||||
throw e;
|
||||
}
|
||||
|
||||
return main;
|
||||
}
|
||||
|
||||
public static IUI 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();
|
||||
getUI().ready();
|
||||
}
|
||||
|
||||
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.");
|
||||
boolean success = false;
|
||||
try {
|
||||
success = DeviceUtils.overwritePreloaded(device, null, 15 * 60);
|
||||
} catch (Exception e) {
|
||||
System.err.println(e);
|
||||
} finally {
|
||||
if (!success) {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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.Main;
|
||||
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) {
|
||||
if (Main.getUI().isSingleThreaded()) {
|
||||
run();
|
||||
} else {
|
||||
new Thread(this).start();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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 AbstractThreadedAction {
|
||||
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;
|
||||
}
|
||||
|
||||
super.actionPerformed(e);
|
||||
}
|
||||
|
||||
@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) {
|
||||
File f = Main.getUI().showSaveDialog();
|
||||
if (f != null) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class ExportAction extends AbstractThreadedAction {
|
||||
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) {
|
||||
super.actionPerformed(e);
|
||||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* 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 AbstractThreadedAction {
|
||||
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) {
|
||||
super.actionPerformed(e);
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Runnable r = new RunMonkeyRunnable(packages);
|
||||
if (Main.getUI().isSingleThreaded()) {
|
||||
r.run();
|
||||
} else {
|
||||
new Thread(r).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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* 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() + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.DeviceUtils;
|
||||
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.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
public class WritePreloadedClassesAction extends AbstractThreadedDeviceSpecificAction {
|
||||
private File preloadedClassFile;
|
||||
|
||||
public WritePreloadedClassesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
|
||||
super("Write preloaded classes action", device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
File[] files = Main.getUI().showOpenDialog(true);
|
||||
if (files != null && files.length > 0) {
|
||||
preloadedClassFile = files[0];
|
||||
super.actionPerformed(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Main.getUI().showWaitDialog();
|
||||
try {
|
||||
// Write the new file with a 5-minute timeout
|
||||
DeviceUtils.overwritePreloaded(device, preloadedClassFile, 5 * 60);
|
||||
} catch (Exception e) {
|
||||
System.err.println(e);
|
||||
} finally {
|
||||
Main.getUI().hideWaitDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* 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) {
|
||||
if (target != null) {
|
||||
target.delete();
|
||||
}
|
||||
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) {
|
||||
if (target != null) {
|
||||
target.delete();
|
||||
}
|
||||
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);
|
||||
} finally {
|
||||
hprofLocalFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.android.preload.ui;
|
||||
|
||||
import com.android.ddmlib.Client;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.ListModel;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
/**
|
||||
* UI abstraction for the tool. This allows a graphical mode, command line mode,
|
||||
* or silent mode.
|
||||
*/
|
||||
public interface IUI {
|
||||
|
||||
void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
|
||||
List<Action> actions);
|
||||
|
||||
void ready();
|
||||
|
||||
boolean isSingleThreaded();
|
||||
|
||||
Client getSelectedClient();
|
||||
|
||||
int getSelectedDataTableRow();
|
||||
|
||||
void showWaitDialog();
|
||||
|
||||
void updateWaitDialog(String s);
|
||||
|
||||
void hideWaitDialog();
|
||||
|
||||
void showMessageDialog(String s);
|
||||
|
||||
boolean showConfirmDialog(String title, String message);
|
||||
|
||||
String showInputDialog(String message);
|
||||
|
||||
<T> T showChoiceDialog(String title, String message, T[] choices);
|
||||
|
||||
File showSaveDialog();
|
||||
|
||||
File[] showOpenDialog(boolean multi);
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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() {}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
package com.android.preload.ui;
|
||||
|
||||
import com.android.ddmlib.Client;
|
||||
import com.android.ddmlib.ClientData;
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.ListModel;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
public class SequenceUI implements IUI {
|
||||
|
||||
private ListModel<Client> clientListModel;
|
||||
@SuppressWarnings("unused")
|
||||
private TableModel dataTableModel;
|
||||
private List<Action> actions;
|
||||
|
||||
private List<Object> sequence = new LinkedList<>();
|
||||
|
||||
public SequenceUI() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleThreaded() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
|
||||
List<Action> actions) {
|
||||
this.clientListModel = clientListModel;
|
||||
this.dataTableModel = dataTableModel;
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
public SequenceUI action(Action a) {
|
||||
sequence.add(a);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SequenceUI action(Class<? extends Action> actionClass) {
|
||||
for (Action a : actions) {
|
||||
if (actionClass.equals(a.getClass())) {
|
||||
sequence.add(a);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No action of class " + actionClass + " found.");
|
||||
}
|
||||
|
||||
public SequenceUI confirmYes() {
|
||||
sequence.add(Boolean.TRUE);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SequenceUI confirmNo() {
|
||||
sequence.add(Boolean.FALSE);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SequenceUI input(String input) {
|
||||
sequence.add(input);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SequenceUI input(File... f) {
|
||||
sequence.add(f);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SequenceUI output(File f) {
|
||||
sequence.add(f);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SequenceUI tableRow(int i) {
|
||||
sequence.add(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
private class ClientSelector {
|
||||
private String pkg;
|
||||
|
||||
public ClientSelector(String pkg) {
|
||||
this.pkg = pkg;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
for (int i = 0; i < clientListModel.getSize(); i++) {
|
||||
ClientData cd = clientListModel.getElementAt(i).getClientData();
|
||||
if (cd != null) {
|
||||
String s = cd.getClientDescription();
|
||||
if (pkg.equals(s)) {
|
||||
return clientListModel.getElementAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Didn't find client " + pkg);
|
||||
}
|
||||
}
|
||||
|
||||
public SequenceUI client(String pkg) {
|
||||
sequence.add(new ClientSelector(pkg));
|
||||
return this;
|
||||
}
|
||||
|
||||
public SequenceUI choice(String pattern) {
|
||||
sequence.add(pattern);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ready() {
|
||||
// Run the actions.
|
||||
// No iterator or foreach loop as the sequence will be emptied while running.
|
||||
try {
|
||||
while (!sequence.isEmpty()) {
|
||||
Object next = sequence.remove(0);
|
||||
if (next instanceof Action) {
|
||||
((Action)next).actionPerformed(null);
|
||||
} else {
|
||||
throw new IllegalStateException("Didn't expect a non-action: " + next);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.out);
|
||||
}
|
||||
|
||||
// Now shut down.
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Client getSelectedClient() {
|
||||
Object next = sequence.remove(0);
|
||||
if (next instanceof ClientSelector) {
|
||||
return ((ClientSelector)next).getClient();
|
||||
}
|
||||
throw new IllegalStateException("Unexpected: " + next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSelectedDataTableRow() {
|
||||
Object next = sequence.remove(0);
|
||||
if (next instanceof Integer) {
|
||||
return ((Integer)next).intValue();
|
||||
}
|
||||
throw new IllegalStateException("Unexpected: " + next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showWaitDialog() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateWaitDialog(String s) {
|
||||
System.out.println(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideWaitDialog() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessageDialog(String s) {
|
||||
System.out.println(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showConfirmDialog(String title, String message) {
|
||||
Object next = sequence.remove(0);
|
||||
if (next instanceof Boolean) {
|
||||
return ((Boolean)next).booleanValue();
|
||||
}
|
||||
throw new IllegalStateException("Unexpected: " + next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String showInputDialog(String message) {
|
||||
Object next = sequence.remove(0);
|
||||
if (next instanceof String) {
|
||||
return (String)next;
|
||||
}
|
||||
throw new IllegalStateException("Unexpected: " + next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T showChoiceDialog(String title, String message, T[] choices) {
|
||||
Object next = sequence.remove(0);
|
||||
if (next instanceof String) {
|
||||
String s = (String)next;
|
||||
for (T t : choices) {
|
||||
if (t.toString().contains(s)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
throw new IllegalStateException("Unexpected: " + next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File showSaveDialog() {
|
||||
Object next = sequence.remove(0);
|
||||
if (next instanceof File) {
|
||||
System.out.println(next);
|
||||
return (File)next;
|
||||
}
|
||||
throw new IllegalStateException("Unexpected: " + next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] showOpenDialog(boolean multi) {
|
||||
Object next = sequence.remove(0);
|
||||
if (next instanceof File[]) {
|
||||
return (File[])next;
|
||||
}
|
||||
throw new IllegalStateException("Unexpected: " + next);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
/*
|
||||
* 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 SwingUI extends JFrame implements IUI {
|
||||
|
||||
private JList<Client> clientList;
|
||||
private JTable dataTable;
|
||||
|
||||
// Shared file chooser, means the directory is retained.
|
||||
private JFileChooser jfc;
|
||||
|
||||
public SwingUI() {
|
||||
super("Preloaded-classes computation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleThreaded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
|
||||
List<Action> actions) {
|
||||
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);
|
||||
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ready() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Client getSelectedClient() {
|
||||
return clientList.getSelectedValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSelectedDataTableRow() {
|
||||
return dataTable.getSelectedRow();
|
||||
}
|
||||
|
||||
private JDialog currentWaitDialog = null;
|
||||
|
||||
@Override
|
||||
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.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideWaitDialog() {
|
||||
if (currentWaitDialog != null) {
|
||||
currentWaitDialog.setVisible(false);
|
||||
currentWaitDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user