content: /*
   * 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);
      }
  
  }
  