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