* Requires root, disable-verity, reboot, root before use. Test: manual through UI and sequencing Change-Id: I68965334776e130b8220a5814b2525109cf96800
342 lines
12 KiB
Java
342 lines
12 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
|
|
}
|