/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.preload;
import com.android.ddmlib.Client;
import com.android.ddmlib.IDevice;
import com.android.preload.actions.ClearTableAction;
import com.android.preload.actions.ComputeThresholdAction;
import com.android.preload.actions.ComputeThresholdXAction;
import com.android.preload.actions.DeviceSpecific;
import com.android.preload.actions.ExportAction;
import com.android.preload.actions.ImportAction;
import com.android.preload.actions.ReloadListAction;
import com.android.preload.actions.RunMonkeyAction;
import com.android.preload.actions.ScanAllPackagesAction;
import com.android.preload.actions.ScanPackageAction;
import com.android.preload.actions.ShowDataAction;
import com.android.preload.classdataretrieval.ClassDataRetriever;
import com.android.preload.classdataretrieval.hprof.Hprof;
import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever;
import com.android.preload.ui.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";
/**
* @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 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()));
}
}
} 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.");
if (!DeviceUtils.removePreloaded(device, 15 * 60) /* 15m timeout */) {
Main.getUI().showMessageDialog("Removing preloaded-classes failed unexpectedly!");
}
}
}
public static Map<String, String> findAndGetClassData(IDevice device, String packageName)
throws Exception {
Client client = clientUtils.findClient(device, packageName, -1);
if (client == null) {
throw new RuntimeException("Could not find client...");
}
System.out.println("Found client: " + client);
return getClassDataRetriever().getClassData(client);
}
}