/*
 * Copyright 2017-present Facebook, Inc.
 *
 * 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.facebook.buck.android.exopackage;

import com.android.ddmlib.InstallException;
import com.facebook.buck.android.agent.util.AgentUtil;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.SimplePerfEvent;
import com.facebook.buck.log.Logger;
import com.google.common.base.Throwables;
import java.io.File;
import java.nio.file.Path;
import java.util.Optional;

class ExopackageAgent {
  private static final Logger LOG = Logger.get(ExopackageInstaller.class);

  /** Prefix of the path to the agent apk on the device. */
  private static final String AGENT_DEVICE_PATH = "/data/app/" + AgentUtil.AGENT_PACKAGE_NAME;
  /** Command line to invoke the agent on the device. */
  static final String JAVA_AGENT_COMMAND =
      "dalvikvm -classpath "
          + AGENT_DEVICE_PATH
          + "-1.apk:"
          + AGENT_DEVICE_PATH
          + "-2.apk:"
          + AGENT_DEVICE_PATH
          + "-1/base.apk:"
          + AGENT_DEVICE_PATH
          + "-2/base.apk "
          + "com.facebook.buck.android.agent.AgentMain ";

  private boolean useNativeAgent;
  private final String nativeAgentPath;

  public ExopackageAgent(boolean useNativeAgent, String nativeAgentPath) {
    this.useNativeAgent = useNativeAgent;
    this.nativeAgentPath = nativeAgentPath;
  }

  /**
   * Sets {@link #useNativeAgent} to true on pre-L devices, because our native agent is built
   * without -fPIC. The java agent works fine on L as long as we don't use it for mkdir.
   */
  private static boolean determineBestAgent(ExopackageDevice device) throws Exception {
    String value = device.getProperty("ro.build.version.sdk");
    try {
      if (Integer.valueOf(value.trim()) > 19) {
        return false;
      }
    } catch (NumberFormatException exn) {
      return false;
    }
    return true;
  }

  String getAgentCommand() {
    if (useNativeAgent) {
      return nativeAgentPath + "/libagent.so ";
    } else {
      return JAVA_AGENT_COMMAND;
    }
  }

  public String getMkDirCommand() {
    return useNativeAgent ? getAgentCommand() + "mkdir-p" : "mkdir -p";
  }

  public static ExopackageAgent installAgentIfNecessary(
      BuckEventBus eventBus, ExopackageDevice device, Path agentApkPath) {
    try {
      Optional<PackageInfo> agentInfo = device.getPackageInfo(AgentUtil.AGENT_PACKAGE_NAME);
      if (agentInfo.isPresent()
          && agentInfo.get().versionCode.equals(AgentUtil.AGENT_VERSION_CODE)) {
        LOG.debug(
            "Agent version mismatch. Wanted %s, got %s.",
            AgentUtil.AGENT_VERSION_CODE, agentInfo.get().versionCode);
        // Always uninstall before installing.  We might be downgrading, which requires
        // an uninstall, or we might just want a clean installation.
        uninstallAgent(eventBus, device);
        agentInfo = Optional.empty();
      }
      if (!agentInfo.isPresent()) {
        LOG.debug("Installing agent.");
        installAgentApk(eventBus, device, agentApkPath);
        agentInfo = device.getPackageInfo(AgentUtil.AGENT_PACKAGE_NAME);
      }
      return new ExopackageAgent(determineBestAgent(device), agentInfo.get().nativeLibPath);
    } catch (Exception e) {
      Throwables.throwIfUnchecked(e);
      throw new RuntimeException(e);
    }
  }

  private static void uninstallAgent(BuckEventBus eventBus, ExopackageDevice device)
      throws InstallException {
    try (SimplePerfEvent.Scope ignored = SimplePerfEvent.scope(eventBus, "uninstall_old_agent")) {
      device.uninstallPackage(AgentUtil.AGENT_PACKAGE_NAME);
    }
  }

  private static void installAgentApk(
      BuckEventBus eventBus, ExopackageDevice device, Path agentApkPath) throws Exception {
    try (SimplePerfEvent.Scope ignored = SimplePerfEvent.scope(eventBus, "install_agent_apk")) {
      File apkPath = agentApkPath.toFile();
      boolean success =
          device.installApkOnDevice(apkPath, /* installViaSd */ false, /* quiet */ false);
      if (!success) {
        throw new RuntimeException();
      }
    }
  }
}

@@ -1100,599 +1100,72 @@
 %0A%0A  
-/** Prefix of the path to the agent apk on the device. */%0A  private static final String AGENT_DEVICE_PATH = %22/data/app/%22 + AgentUtil.AGENT_PACKAGE_NAME;%0A  /** Command line to invoke the agent on the device. */%0A  static final String JAVA_AGENT_COMMAND =%0A      %22dalvikvm -
+private boolean useNativeAgent;%0A  private final String 
 class
-p
+P
 ath
- %22%0A          + AGENT_DEVICE_PATH%0A          + %22-1.apk:%22%0A          + AGENT_DEVICE_PATH%0A          + %22-2.apk:%22%0A          + AGENT_DEVICE_PATH%0A          + %22-1/base.apk:%22%0A          + AGENT_DEVICE_PATH%0A          + %22-2/base.apk %22%0A          + %22com.facebook.buck.android.agent.AgentMain %22;%0A%0A  private boolean useNativeAgent
 ;%0A  
@@ -1251,16 +1251,34 @@
 veAgent,
+ String classPath,
  String 
@@ -1338,16 +1338,48 @@
 eAgent;%0A
+    this.classPath = classPath;%0A
     this
@@ -2076,26 +2076,90 @@
 urn 
-JAVA_AGENT_COMMAND
+%22dalvikvm -classpath %22 + classPath + %22 com.facebook.buck.android.agent.AgentMain %22
 ;%0A  
@@ -3269,16 +3269,27 @@
 geAgent(
+%0A          
 determin
@@ -3307,16 +3307,41 @@
 device),
+ agentInfo.get().apkPath,
  agentIn

Fix exopackage installer on O